diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml index fc2212a..887f854 100644 --- a/.github/workflows/backend.yml +++ b/.github/workflows/backend.yml @@ -24,7 +24,7 @@ jobs: SSH_HOST: skillproof.hu SSH_USER: root # --- SECRET-ek --- - # SSH_KEY, SSH_PASS, DB_CONN, DESIRED_PORT + # SSH_KEY, SSH_PASS, DB_CONN, DESIRED_PORT, API_KEY steps: - name: Checkout @@ -107,7 +107,8 @@ jobs: --arg frontend "${{ env.FRONTEND_URL }}" \ --arg jwtkey "${{ secrets.JWT_KEY }}" \ --arg port "${{ secrets.DESIRED_PORT }}" \ - '.db.conn = $conn | .settings.frontend = $frontend | .settings.port = $port | .Jwt.Key = $jwtkey' \ + --arg apikey "${{ secrets.API_KEY }}" \ + '.db.conn = $conn | .settings.frontend = $frontend | .settings.port = $port | .Jwt.Key = $jwtkey | .Gemini.ApiKey = $apikey' \ "$NEW_RELEASE/appsettings.json" | sudo tee "$NEW_RELEASE/appsettings.json.tmp" > /dev/null sudo mv "$NEW_RELEASE/appsettings.json.tmp" "$NEW_RELEASE/appsettings.json" diff --git a/.github/workflows/testBackend.yml b/.github/workflows/testBackend.yml index aab829a..9b677fc 100644 --- a/.github/workflows/testBackend.yml +++ b/.github/workflows/testBackend.yml @@ -24,7 +24,7 @@ jobs: SSH_HOST: test.skillproof.hu SSH_USER: root # --- SECRET-ek (Repo Settings → Secrets and variables → Actions) --- - # SSH_KEY, SSH_PASS, DB_CONN_TEST, DESIRED_PORT_TEST + # SSH_KEY, SSH_PASS, DB_CONN_TEST, DESIRED_PORT_TEST, API_KEY steps: - name: Checkout @@ -107,7 +107,8 @@ jobs: --arg frontend "${{ env.FRONTEND_URL }}" \ --arg jwtkey "${{ secrets.JWT_KEY }}" \ --arg port "${{ secrets.DESIRED_PORT_TEST }}" \ - '.db.conn = $conn | .settings.frontend = $frontend | .settings.port = $port | .Jwt.Key = $jwtkey' \ + --arg apikey "${{ secrets.API_KEY }}" \ + '.db.conn = $conn | .settings.frontend = $frontend | .settings.port = $port | .Jwt.Key = $jwtkey | .Gemini.ApiKey = $apikey' \ "$NEW_RELEASE/appsettings.json" | sudo tee "$NEW_RELEASE/appsettings.json.tmp" > /dev/null sudo mv "$NEW_RELEASE/appsettings.json.tmp" "$NEW_RELEASE/appsettings.json" else diff --git a/README.md b/README.md index 0c31a71..b246dfc 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,81 @@ -# skillproof -## Adatbázis beállítása helyi fejlesztéshez (Docker) +# Skillproof – Kompetencia-alapú toborzási platform -Mivel a projekt Linux/Mac környezetben is futtatható, az adatbázist (MSSQL) egy Docker konténerben futtatjuk. Nincs szükség bonyolult lokális telepítésekre! +A Skillproof egy modern, professzionális hálózatépítő és álláskereső webalkalmazás, amely a munkáltatókat és a munkavállalókat köti össze. A platform fókuszában a tudásalapú kiválasztás áll: a cégek az álláshirdetésekhez saját kompetenciateszteket rendelhetnek, amelyeket a jelölteknek a jelentkezés során ki kell tölteniük. -### Előfeltételek -* **Docker** és **Docker Compose** telepítve legyen a gépeden. -* **.NET EF Core Tools** telepítve legyen globálisan vagy lokálisan (`dotnet tool install dotnet-ef`). +## Célkitűzések + +- Objektív, valós tudáson alapuló szűrés biztosítása a munkáltatók számára. +- A jelentkezési és kiválasztási folyamat hatékonyságának növelése. +- Átlátható felület biztosítása a pályázóknak a képességeik bizonyítására és az ideális pozíciók megtalálására. + +## Technológiai Stack és Fejlesztési Környezet + +- **Backend:** C# .NET 9 +- **Frontend:** Angular 21 +- **Adatbázis:** MSSQL + +### Adatbázis indítása és futtatás + +A backend elindításához és az adatbázis migrációk futtatásához nyiss egy terminált a szerver oldali gyökérmappában: -### 1. Adatbázis szerver indítása -Nyiss egy terminált a projekt gyökerében (ahol a `docker-compose.yml` van), és futtasd ezt: ```bash -docker compose up -d +dotnet tool install --global dotnet-ef +dotnet ef database update +dotnet run +``` + +A frontend indítása a kliens mappában: + +```bash +npm install +ng serve +``` + +## Szerepkörök és Felhasználók + +- **Munkáltató (B2B):** Álláshirdetések és az azokhoz tartozó tesztek létrehozása, jelentkezők kezelése és eredményeik kiértékelése. +- **Pályázó (B2C):** Profil menedzselése, állások böngészése, mentése, tesztek kitöltése és jelentkezés a kiválasztott pozíciókra. +- **Rendszer adminisztrátor (Admin):** A platform globális felügyelete. + +## Csapat és Felelősségek + +- **[@BenjaminKovacs09](https://github.com/BenjaminKovacs09)** – Project Manager +- **[@Marci260](https://github.com/Marci260)** – Architect +- **[@zadoriaron](https://github.com/zadoriaron)** – Fullstack fejlesztő +- **[@KelemenOzseb](https://github.com/KelemenOzseb)** – Fullstack fejlesztő +- **[@oli-tolnai](https://github.com/oli-tolnai)** – Fullstack fejlesztő +- **[@AdamRevesz](https://github.com/AdamRevesz)** – Fullstack fejlesztő + +## Tervezett funkciók + +### 1. Publikus felületek + +- Értékajánlat kommunikálása (gyorsabb, tesztalapú kiválasztás). +- Regisztráció és bejelentkezés különválasztott munkáltatói és pályázói folyamattal. + +### 2. Munkáltatói funkciók + +- **Munkáltatói Dashboard:** Az aktív és lezárt hirdetések kártyás áttekintése. +- **Álláshirdetések kezelése:** + - Új pozíciók létrehozása (cím, leírás, elvárások). + - Meglévő hirdetések módosítása vagy törlése. +- **Teszt- és kérdésbank:** + - Kérdések és komplett kompetenciatesztek összeállítása. + - Tesztek hozzárendelése konkrét álláshirdetésekhez. +- **Jelentkezések adminisztrációja:** + - Pályázók listázása egy adott pozícióra. + - A kitöltött teszteredmények megtekintése a jelentkezők profilja mellett. + - AI asszisztált kiértékelés: A kifejtős kérdésekre adott válaszokat a rendszer mesterséges intelligencia segítségével előzetesen kiértékeli, amelyet a munkáltató felülvizsgálhat és szükség esetén manuálisan felülírhat. + +### 3. Pályázói funkciók + +- **Profilkezelés:** + - Szakmai tapasztalatok, készségek és személyes adatok szerkesztése. + - Skillek hozzárendelése a profilhoz, vannak olyan skillek amelyeket teszt kitöltésével érhet el a felhasználó +- **Álláskeresés:** + - Elérhető pozíciók listázása és részleteik megtekintése. + - Állások kedvencekhez adása (könyvjelzőzés későbbi megtekintésre). +- **Jelentkezési folyamat:** + - Pályázás indítása a felületen keresztül. + - Kompetenciateszt kitöltése: Amennyiben a munkáltató tesztet rendelt az álláshoz, annak integrált kitöltése a jelentkezési folyamat részeként. + - Sikeres jelentkezés véglegesítése és visszajelzés a felhasználónak. diff --git a/backend/SkillProof/SkillProof.Api/Controllers/AssessmentsController.cs b/backend/SkillProof/SkillProof.Api/Controllers/AssessmentsController.cs index fc631f7..0da2349 100644 --- a/backend/SkillProof/SkillProof.Api/Controllers/AssessmentsController.cs +++ b/backend/SkillProof/SkillProof.Api/Controllers/AssessmentsController.cs @@ -71,5 +71,19 @@ public async Task AssignAssessmentToJob(string assessmentId, stri await _assessmentLogic.AssignAssessmentToJob(assessmentId, jobId); return Ok(new { message = "Assessment assigned to job successfully." }); } + + [HttpPost("assign-to-skill")] + public async Task AddAssesmentToSkill([FromBody] AddAssesmentsToSkillDto dto) + { + await _assessmentLogic.AddAssessmentToSkill(dto); + } + + [HttpGet("GetAssesmentBySkill")] + public async Task> GetAssessmentBySkill(string skillId) + { + var assesment = await _assessmentLogic.GetAssessmentBySkill(skillId); + + return assesment; + } } } \ No newline at end of file diff --git a/backend/SkillProof/SkillProof.Api/Controllers/EducationController.cs b/backend/SkillProof/SkillProof.Api/Controllers/EducationController.cs new file mode 100644 index 0000000..4bb29dc --- /dev/null +++ b/backend/SkillProof/SkillProof.Api/Controllers/EducationController.cs @@ -0,0 +1,53 @@ +using Microsoft.AspNetCore.Mvc; +using SkillProof.Logic.Education; +using SkillProof.Entities.Dtos.Education; + +namespace SkillProof.Api.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class EducationController : ControllerBase + { + private readonly IEducationLogic _educationLogic; + public EducationController(IEducationLogic educationLogic) + { + _educationLogic = educationLogic; + } + + [HttpGet("{userId}")] + public async Task GetEducationsByUserId(string userId) + { + var educations = await _educationLogic.GetEducationsByUserIdAsync(userId); + return Ok(educations); + } + + [HttpPost("{userId}")] + public async Task CreateEducation(string userId, [FromBody] EducationCreateDto entity) + { + try + { + var createdEducation = await _educationLogic.CreateEducationAsync(entity, userId); + return Created("", createdEducation); + } + catch (Exception ex) when (ex.Message == "User not found") + { + return NotFound(new { message = "The specified user ID does not exist in the database." }); + } + catch (Exception ex) + { + return StatusCode(500, new + { + message = "An error occurred while saving to the database.", + details = ex.InnerException?.Message ?? ex.Message + }); + } + } + + [HttpDelete("{userId}/{id}")] + public async Task DeleteEducation(string userId, string id) + { + await _educationLogic.DeleteEducationAsync(id, userId); + return NoContent(); + } + } +} diff --git a/backend/SkillProof/SkillProof.Api/Controllers/ExperienceControllers.cs b/backend/SkillProof/SkillProof.Api/Controllers/ExperienceControllers.cs new file mode 100644 index 0000000..4e9271e --- /dev/null +++ b/backend/SkillProof/SkillProof.Api/Controllers/ExperienceControllers.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using SkillProof.Logic.Experience; +using SkillProof.Entities.Dtos.Experience; +namespace SkillProof.Api.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class ExperienceController : ControllerBase + { + private readonly IExperienceLogic _experienceLogic; + + public ExperienceController(IExperienceLogic experienceLogic) + { + _experienceLogic = experienceLogic; + } + + [HttpGet("{userId}")] + public async Task GetExperiencesByUserId(string userId) + { + var experiences = await _experienceLogic.GetExperiencesByUserIdAsync(userId); + return Ok(experiences); + } + + [HttpPost("{userId}")] + public async Task CreateExperience(string userId, [FromBody] ExperienceCreateDto entity) + { + var createdExperience = await _experienceLogic.CreateExperienceAsync(entity, userId); + return CreatedAtAction(nameof(GetExperiencesByUserId), new { userId }, createdExperience); + } + + [HttpDelete("{userId}/{id}")] + public async Task DeleteExperience(string userId, string id) + { + await _experienceLogic.DeleteExperienceAsync(id, userId); + return NoContent(); + } + } +} diff --git a/backend/SkillProof/SkillProof.Api/Controllers/GeminiController.cs b/backend/SkillProof/SkillProof.Api/Controllers/GeminiController.cs new file mode 100644 index 0000000..ceab954 --- /dev/null +++ b/backend/SkillProof/SkillProof.Api/Controllers/GeminiController.cs @@ -0,0 +1,45 @@ +using Microsoft.AspNetCore.Mvc; +using SkillProof.Entities.Dtos.Gemini; +using SkillProof.Entities.Models.Gemini; +using SkillProof.Logic.Gemini; + +namespace SkillProof.Api.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class GradingController : ControllerBase + { + private readonly IGeminiService _gradingService; + + public GradingController(IGeminiService gradingService) + { + _gradingService = gradingService; + } + + [HttpPost("grade")] + public async Task Grade([FromBody] GradingRequest request) + { + if (string.IsNullOrWhiteSpace(request.StudentAnswer)) + return BadRequest("Student answer cannot be empty."); + + var result = await _gradingService.EvaluateAnswerAsync(request); + return Ok(result); + } + + [HttpPost("grade-multiple")] + public async Task GradeMultiple([FromBody] List requests) + { + if (requests == null || !requests.Any()) + return BadRequest("Request list cannot be empty."); + var results = new List(); + foreach (var request in requests) + { + if (string.IsNullOrWhiteSpace(request.StudentAnswer)) + return BadRequest("Student answer cannot be empty."); + var score = await _gradingService.EvaluateAnswerAsync(request); + results.Add(score); + } + return Ok(results); + } + } +} diff --git a/backend/SkillProof/SkillProof.Api/Controllers/JobsController.cs b/backend/SkillProof/SkillProof.Api/Controllers/JobsController.cs index 3feba62..7826abb 100644 --- a/backend/SkillProof/SkillProof.Api/Controllers/JobsController.cs +++ b/backend/SkillProof/SkillProof.Api/Controllers/JobsController.cs @@ -1,3 +1,4 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using SkillProof.Entities.Dtos.Assesment; using SkillProof.Entities.Dtos.Job; @@ -5,6 +6,7 @@ using SkillProof.Entities.Dtos.Questions; using SkillProof.Entities.Models; using SkillProof.Logic.Jobs; +using System.Security.Claims; namespace SkillProof.Api.Controllers; [Route("api/[controller]")] @@ -63,6 +65,7 @@ public async Task GetJobsByCompany(string companyId) [HttpPut("{id}")] public async Task UpdateJob(string id, [FromBody] JobViewDto dto) { + var companyId = User.Claims.FirstOrDefault(c => c.Type == "CompanyId" || c.Type.EndsWith("CompanyId", StringComparison.OrdinalIgnoreCase))?.Value; @@ -102,4 +105,100 @@ public async Task GetCandidateTest(string id) return Ok(candidateTest); } + [HttpPost("{id}/apply")] + public async Task ApplyForJob(string id) + { + var userId = User.Claims.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; + if (string.IsNullOrEmpty(userId)) + { + return Unauthorized(new { message = "You must be logged in to apply." }); + } + + try + { + var applicationId = await _jobLogic.ApplyForJobAsync(id, userId); + return Ok(new { ApplicationId = applicationId, message = "Application submitted successfully." }); + } + catch (KeyNotFoundException ex) + { + return NotFound(new { message = ex.Message }); + } + catch (InvalidOperationException ex) + { + return Conflict(new { message = ex.Message }); + } + catch (Exception ex) + { + return StatusCode(500, new { message = "An error occurred while submitting the application.", details = ex.Message }); + } + } + + [HttpPut("{id}/accept")] + public async Task AcceptCandidate([FromQuery] string userId, string id) + { + var companyId = User.Claims.FirstOrDefault(c => + c.Type == "CompanyId" || + c.Type.EndsWith("CompanyId", StringComparison.OrdinalIgnoreCase))?.Value; + + if (string.IsNullOrEmpty(companyId)) + { + return BadRequest(new { message = "Company ID is missing from the authentication token." }); + } + + await _jobLogic.AcceptCandidateAsync(userId, id); + return Ok(); + } + + [HttpPut("{id}/reject")] + public async Task RejectCandidate([FromQuery] string userId, string id) + { + var companyId = User.Claims.FirstOrDefault(c => + c.Type == "CompanyId" || + c.Type.EndsWith("CompanyId", StringComparison.OrdinalIgnoreCase))?.Value; + + if (string.IsNullOrEmpty(companyId)) + { + return BadRequest(new { message = "Company ID is missing from the authentication token." }); + } + + await _jobLogic.RejectCandidateAsync(userId, id); + return Ok(); + } + + [HttpGet("notifications")] + [Authorize] + public async Task GetNotifications() + { + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (string.IsNullOrEmpty(userId)) + { + return Unauthorized(); + } + + var notifications = await _jobLogic.GetNotificationsAsync(userId); + return Ok(notifications); + } + + [HttpPut("notifications/{id}/read")] + [Authorize] + public async Task MarkAsRead(string id) + { + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (string.IsNullOrEmpty(userId)) + { + return Unauthorized(); + } + + try + { + await _jobLogic.MarkNotificationAsReadAsync(id, userId); + return Ok(); + } + catch (KeyNotFoundException ex) + { + return NotFound(new { message = ex.Message }); + } + } + + } \ No newline at end of file diff --git a/backend/SkillProof/SkillProof.Api/Controllers/SkillController.cs b/backend/SkillProof/SkillProof.Api/Controllers/SkillController.cs new file mode 100644 index 0000000..a239df6 --- /dev/null +++ b/backend/SkillProof/SkillProof.Api/Controllers/SkillController.cs @@ -0,0 +1,77 @@ +using Microsoft.AspNetCore.Mvc; +using System.Security.Claims; +using SkillProof.Entities.Dtos; +using SkillProof.Entities.Dtos.Skill; +using SkillProof.Logic.Skill; +using Microsoft.AspNetCore.Authorization; + + +namespace SkillProof.Api.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class SkillController : ControllerBase + { + public readonly SkillLogic _skillLogic; + + public SkillController(SkillLogic skillLogic) + { + this._skillLogic = skillLogic; + } + + [HttpGet] + public async Task>> GetAllSkills() + { + var skills = await _skillLogic.GetAllSkillsAsync(); + return Ok(skills); + } + + [HttpGet("{id}")] + public async Task GetSkillById(string id) + { + var skill = await _skillLogic.GetSkillByIdAsync(id); + return Ok(skill); + } + + [HttpPost] + [Authorize(Roles = "Admin")] + public async Task CreateSkillAsync([FromBody] SkillCreateDto model) + { + var userId = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value; + + if (string.IsNullOrEmpty(userId)) + { + return Unauthorized(new { message = "User ID is missing from the token." }); + } + + var result = await _skillLogic.CreateSkillAsync(model); + return CreatedAtAction(nameof(GetSkillById), new { id = result.Id }, result); + + } + + [HttpDelete("{id}")] + [Authorize(Roles = "Admin")] + public async Task DeleteSkill(string id) + { + try + { + await _skillLogic.DeleteSkill(id); + return Ok(); + } + catch (Exception ex) + { + return NotFound(ex.Message); + } + } + [HttpGet("{id}/test/{assessmentId}")] + public async Task GetTestForSkill(string id, string assessmentId) + { + var test = await _skillLogic.GetCandidateTestForSkill(id, assessmentId); + if (test == null) return NotFound(new { message = "No test available for this skill/assessment level." }); + return Ok(test); + } + + + + } +} diff --git a/backend/SkillProof/SkillProof.Api/Controllers/TestsController.cs b/backend/SkillProof/SkillProof.Api/Controllers/TestsController.cs index 2ad9dcd..153aaae 100644 --- a/backend/SkillProof/SkillProof.Api/Controllers/TestsController.cs +++ b/backend/SkillProof/SkillProof.Api/Controllers/TestsController.cs @@ -28,4 +28,49 @@ public async Task> SubmitTest([FromBody] TestSubmitD var result = await _testLogic.SubmitTestAsync(dto, userId); return Ok(result); } + + [HttpPost("submitSkillTest")] + public async Task> SubmitSkillTest([FromBody] TestSubmitSkillDto dto) + { + var userId = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value; + if (string.IsNullOrEmpty(userId)) + { + return Unauthorized(new { message = "You must be logged in to submit a skill test." }); + } + + try + { + var result = await _testLogic.SubmitTestSkillAsync(dto, userId); + return Ok(result); + } + catch (KeyNotFoundException ex) + { + return NotFound(new { message = ex.Message }); + } + catch (ArgumentException ex) + { + return BadRequest(new { message = ex.Message }); + } + } + + [HttpGet("GetUserTestQuestions")] + public async Task>> GetUserTestQuestions(string userId, string jobId) + { + var result = await _testLogic.GetUserTestQuestionsAsync(jobId, userId); + return Ok(result); + } + + [HttpGet("GetTestUsers")] + public async Task>> GetTestUsers(string jobId) + { + var result = await _testLogic.GetTestUsersAsync(jobId); + return Ok(result); + } + + [HttpPut("ManualFeedbackAsync")] + public async Task> ManualFeedback([FromBody] string? feedback, double score, string testAnswerId) + { + var result = await _testLogic.ManualFeedbackAsync(feedback, score, testAnswerId); + return Ok(result); + } } diff --git a/backend/SkillProof/SkillProof.Api/Controllers/UserController.cs b/backend/SkillProof/SkillProof.Api/Controllers/UserController.cs index ff50901..49c655f 100644 --- a/backend/SkillProof/SkillProof.Api/Controllers/UserController.cs +++ b/backend/SkillProof/SkillProof.Api/Controllers/UserController.cs @@ -1,18 +1,7 @@ using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http.HttpResults; -using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Options; -using Microsoft.IdentityModel.Tokens; using SkillProof.Entities.Dtos.Users; -using SkillProof.Entities.Helper; -using SkillProof.Entities.Models; -using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; using SkillProof.Logic.User; namespace SkillProof.Api.Controllers @@ -91,5 +80,69 @@ public async Task RevokeRole(string userId) await _userLogic.RevokeRoleAsync(userId); return Ok(new { message = "Roles revoked successfully." }); } + + [HttpGet("UserTests/{userId}")] + public async Task GetUserTests(string userId) + { + var tests = await _userLogic.GetUserTestsAsync(userId); + return Ok(tests); + } + + [HttpPost("{id}/skills")] + public async Task UpdateSkillsToUser(string id, [FromBody] string[] skillId) + { + try + { + await _userLogic.UpdateSkillsToUser(id, skillId); + return Ok(new { message = "Skill successfully added to user." }); + } + catch (KeyNotFoundException ex) + { + return NotFound(new { message = ex.Message }); + } + catch (InvalidOperationException ex) + { + return BadRequest(new { message = ex.Message }); + } + } + + [HttpPost("toggle-saved-job/{jobId}")] + public async Task ToggleSavedJob(string jobId) + { + var currentUserId = User.FindFirstValue(ClaimTypes.NameIdentifier); + var updatedProfile = await _userLogic.ToggleSavedJobAsync(currentUserId, jobId); + return Ok(updatedProfile); + } + + [HttpPost("apply/{jobId}")] + public async Task ApplyToJob(string jobId) + { + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (string.IsNullOrEmpty(userId)) return Unauthorized(); + + try + { + await _userLogic.ApplyToJobAsync(userId, jobId); + return Ok(); + } + catch (InvalidOperationException ex) + { + return BadRequest(new { message = ex.Message }); + } + } + + [HttpDelete("remove-skill/{skillId}/{userId}")] + public async Task RemoveSkill(string skillId, string userId) + { + try + { + await _userLogic.DeleteSkillFromUser(userId, skillId); + return Ok(new { message = "Skill removed successfully." }); + } + catch (KeyNotFoundException ex) + { + return NotFound(new { message = ex.Message }); + } + } } } diff --git a/backend/SkillProof/SkillProof.Api/Program.cs b/backend/SkillProof/SkillProof.Api/Program.cs index ffc6d25..1e61aa1 100644 --- a/backend/SkillProof/SkillProof.Api/Program.cs +++ b/backend/SkillProof/SkillProof.Api/Program.cs @@ -11,9 +11,13 @@ using SkillProof.Entities.Models; using SkillProof.Logic.Assesments; using SkillProof.Logic.Assessments; +using SkillProof.Logic.Education; +using SkillProof.Logic.Experience; +using SkillProof.Logic.Gemini; using SkillProof.Logic.Helper; using SkillProof.Logic.Jobs; using SkillProof.Logic.Questions; +using SkillProof.Logic.Skill; using SkillProof.Logic.Tests; using SkillProof.Logic.User; using System.IdentityModel.Tokens.Jwt; @@ -70,7 +74,11 @@ public static void Main(string[] args) }); }); builder.Services.AddTransient(typeof(Repository<>)); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddCors(option => { @@ -208,16 +216,16 @@ public static void Main(string[] args) using (var scope = app.Services.CreateScope()) { var services = scope.ServiceProvider; - try - { + //try + //{ var context = services.GetRequiredService(); context.Database.Migrate(); DbInitializer.Seed(context); - } - catch (Exception ex) - { - Console.WriteLine($"Database seeding failed: {ex.Message}"); - } + //} + //catch (Exception ex) + //{ + // Console.WriteLine($"Database seeding failed: {ex.Message}"); + //} } app.Run(); diff --git a/backend/SkillProof/SkillProof.Api/SkillProof.Api.csproj b/backend/SkillProof/SkillProof.Api/SkillProof.Api.csproj index 9e3c750..4e72718 100644 --- a/backend/SkillProof/SkillProof.Api/SkillProof.Api.csproj +++ b/backend/SkillProof/SkillProof.Api/SkillProof.Api.csproj @@ -28,4 +28,8 @@ + + + + diff --git a/backend/SkillProof/SkillProof.Api/appsettings.json b/backend/SkillProof/SkillProof.Api/appsettings.json index 84cfde1..b83f483 100644 --- a/backend/SkillProof/SkillProof.Api/appsettings.json +++ b/backend/SkillProof/SkillProof.Api/appsettings.json @@ -9,6 +9,9 @@ "Jwt": { "Key": "NagyonhosszútitkosítókulcsNagyonhosszútitkosítókulcsNagyonhosszútitkosítókulcsNagyonhosszútitkosítókulcsNagyonhosszútitkosítókulcsNagyonhosszútitkosítókulcs" }, + "Gemini": { + "ApiKey": "Secretből cserélni workflow segítségével" + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/backend/SkillProof/SkillProof.Api/seed-questions.json b/backend/SkillProof/SkillProof.Api/seed-questions.json index 5f98b42..2bfac68 100644 --- a/backend/SkillProof/SkillProof.Api/seed-questions.json +++ b/backend/SkillProof/SkillProof.Api/seed-questions.json @@ -5,7 +5,9 @@ "Difficulty": "Junior", "Title": "C# Case Sensitivity", "QuestionText": "C# is a case-sensitive programming language.", - "TrueFalsePayload": { + "Tags": [ "C#" ], + "CreatedBy": "Seed", + "TrueFalse": { "CorrectAnswer": true, "Explanation": "In C#, 'Variable' and 'variable' are treated as two distinct identifiers." } @@ -16,14 +18,11 @@ "Difficulty": "Junior", "Title": "Reference Types", "QuestionText": "Which of the following are reference types in C#?", - "MultipleChoicePayload": { - "Options": [ - { "Id": "A", "Text": "int" }, - { "Id": "B", "Text": "string" }, - { "Id": "C", "Text": "bool" }, - { "Id": "D", "Text": "class" } - ], - "CorrectAnswerIds": ["B", "D"], + "Tags": [ "C#" ], + "CreatedBy": "Seed", + "MultipleChoice": { + "Options": [ "int", "string", "bool", "class" ], + "CorrectOptionIndexes": [ 1, 3 ], "AllowMultipleSelection": true } }, @@ -33,7 +32,9 @@ "Difficulty": "Junior", "Title": "String Mutability", "QuestionText": "Strings in C# are mutable, meaning they can be changed after they are created.", - "TrueFalsePayload": { + "Tags": [ "C#" ], + "CreatedBy": "Seed", + "TrueFalse": { "CorrectAnswer": false, "Explanation": "Strings are immutable. Any operation that appears to modify a string actually creates a new string object." } @@ -44,14 +45,11 @@ "Difficulty": "Medior", "Title": "Access Modifiers", "QuestionText": "Which access modifier restricts access to the containing class or types derived from the containing class?", - "MultipleChoicePayload": { - "Options": [ - { "Id": "A", "Text": "public" }, - { "Id": "B", "Text": "private" }, - { "Id": "C", "Text": "protected" }, - { "Id": "D", "Text": "internal" } - ], - "CorrectAnswerIds": ["C"], + "Tags": [ "C#" ], + "CreatedBy": "Seed", + "MultipleChoice": { + "Options": [ "public", "private", "protected", "internal" ], + "CorrectOptionIndexes": [ 2 ], "AllowMultipleSelection": false } }, @@ -61,7 +59,9 @@ "Difficulty": "Medior", "Title": "Interface Instantiation", "QuestionText": "You can create a direct instance of an interface using the 'new' keyword.", - "TrueFalsePayload": { + "Tags": [ "C#" ], + "CreatedBy": "Seed", + "TrueFalse": { "CorrectAnswer": false, "Explanation": "Interfaces cannot be instantiated directly. They must be implemented by a class or struct." } @@ -72,14 +72,11 @@ "Difficulty": "Medior", "Title": "Dependency Injection Lifetimes", "QuestionText": "In ASP.NET Core, which service lifetime creates a new instance every time it is requested?", - "MultipleChoicePayload": { - "Options": [ - { "Id": "A", "Text": "Singleton" }, - { "Id": "B", "Text": "Scoped" }, - { "Id": "C", "Text": "Transient" }, - { "Id": "D", "Text": "Static" } - ], - "CorrectAnswerIds": ["C"], + "Tags": [ "C#" ], + "CreatedBy": "Seed", + "MultipleChoice": { + "Options": [ "Singleton", "Scoped", "Transient", "Static" ], + "CorrectOptionIndexes": [ 2 ], "AllowMultipleSelection": false } }, @@ -89,7 +86,9 @@ "Difficulty": "Senior", "Title": "Async / Await Blocking", "QuestionText": "Using the 'await' keyword blocks the calling thread until the awaited task completes.", - "TrueFalsePayload": { + "Tags": [ "C#" ], + "CreatedBy": "Seed", + "TrueFalse": { "CorrectAnswer": false, "Explanation": "'await' yields control back to the calling method, allowing the thread to do other work without blocking." } @@ -100,14 +99,11 @@ "Difficulty": "Senior", "Title": "Garbage Collection Generations", "QuestionText": "How many generations does the .NET Garbage Collector use to group objects by their lifetime?", - "MultipleChoicePayload": { - "Options": [ - { "Id": "A", "Text": "One (Gen 0)" }, - { "Id": "B", "Text": "Two (Gen 0, Gen 1)" }, - { "Id": "C", "Text": "Three (Gen 0, Gen 1, Gen 2)" }, - { "Id": "D", "Text": "Four (Gen 0, Gen 1, Gen 2, Gen 3)" } - ], - "CorrectAnswerIds": ["C"], + "Tags": [ "C#" ], + "CreatedBy": "Seed", + "MultipleChoice": { + "Options": [ "One (Gen 0)", "Two (Gen 0, Gen 1)", "Three (Gen 0, Gen 1, Gen 2)", "Four (Gen 0, Gen 1, Gen 2, Gen 3)" ], + "CorrectOptionIndexes": [ 2 ], "AllowMultipleSelection": false } }, @@ -117,14 +113,11 @@ "Difficulty": "Senior", "Title": "LINQ Execution", "QuestionText": "Which of the following LINQ operators enforce immediate execution?", - "MultipleChoicePayload": { - "Options": [ - { "Id": "A", "Text": "Where()" }, - { "Id": "B", "Text": "ToList()" }, - { "Id": "C", "Text": "Select()" }, - { "Id": "D", "Text": "Count()" } - ], - "CorrectAnswerIds": ["B", "D"], + "Tags": [ "C#" ], + "CreatedBy": "Seed", + "MultipleChoice": { + "Options": [ "Where()", "ToList()", "Select()", "Count()" ], + "CorrectOptionIndexes": [ 1, 3 ], "AllowMultipleSelection": true } }, @@ -134,9 +127,11 @@ "Difficulty": "Medior", "Title": "Értéktípusok", "QuestionText": "A struct (struktúra) a C#-ban referenciatípusnak számít.", - "TrueFalsePayload": { + "Tags": [ "C#" ], + "CreatedBy": "Seed", + "TrueFalse": { "CorrectAnswer": false, "Explanation": "A struct értéktípus (value type), ellentétben a class-szal, ami referenciatípus (reference type)." } } -] +] \ No newline at end of file diff --git a/backend/SkillProof/SkillProof.Data/Migrations/20260319193050_InitialCreate.Designer.cs b/backend/SkillProof/SkillProof.Data/Migrations/20260319193050_InitialCreate.Designer.cs deleted file mode 100644 index 7ad6a95..0000000 --- a/backend/SkillProof/SkillProof.Data/Migrations/20260319193050_InitialCreate.Designer.cs +++ /dev/null @@ -1,800 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using SkillProof.Data; - -#nullable disable - -namespace SkillProof.Data.Migrations -{ - [DbContext(typeof(SkillProofDbContext))] - [Migration("20260319193050_InitialCreate")] - partial class InitialCreate - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.0") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - - modelBuilder.Entity("CompaniesUsers", b => - { - b.Property("CompaniesId") - .HasColumnType("nvarchar(450)"); - - b.Property("UsersId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("CompaniesId", "UsersId"); - - b.HasIndex("UsersId"); - - b.ToTable("CompaniesUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Discriminator") - .IsRequired() - .HasMaxLength(13) - .HasColumnType("nvarchar(13)"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers", (string)null); - - b.HasDiscriminator("Discriminator").HasValue("IdentityUser"); - - b.UseTphMappingStrategy(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", (string)null); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.CodeCompletionQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("AcceptedAnswers") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("CodeSnippet") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("CodeCompletionQuestions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Companies", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Website") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Companies"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.FillInTheBlankQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("Answer") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("manualFeedback") - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("FillInTheBlankQuestions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.JobApplications", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AppliedAt") - .HasColumnType("datetime2"); - - b.Property("JobId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("Status") - .HasColumnType("int"); - - b.Property("TestId") - .HasColumnType("nvarchar(450)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("JobId"); - - b.HasIndex("TestId") - .IsUnique() - .HasFilter("[TestId] IS NOT NULL"); - - b.HasIndex("UserId"); - - b.ToTable("JobApplications"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Jobs", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CompanyId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("EmploymentType") - .HasColumnType("int"); - - b.Property("Location") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Tags") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.HasIndex("CompanyId"); - - b.ToTable("Jobs"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.MultipleChoiceQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("AllowMultipleSelection") - .HasColumnType("bit"); - - b.Property("CorrectAnswerIds") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Options") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("MultipleChoiceQuestion"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Questions", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Difficulty") - .HasColumnType("int"); - - b.Property("IsActive") - .HasColumnType("bit"); - - b.Property("Language") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("QuestionText") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("nvarchar(255)"); - - b.Property("Type") - .HasColumnType("int"); - - b.Property("UpdatedAt") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.ToTable("Questions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TestAnswers", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AiFeedback") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("FreeTextResponse") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("IsCorrect") - .HasColumnType("bit"); - - b.Property("QuestionId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("TestId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("QuestionId"); - - b.HasIndex("TestId"); - - b.ToTable("TestAnswers"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CompletedAt") - .HasColumnType("datetime2"); - - b.Property("DifficultyLevel") - .HasColumnType("int"); - - b.Property("Passed") - .HasColumnType("bit"); - - b.Property("Score") - .HasColumnType("int"); - - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("Tests"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.UserExperiences", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CompanyName") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("EndDate") - .HasColumnType("datetime2"); - - b.Property("JobTitle") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("StartDate") - .HasColumnType("datetime2"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("isVerified") - .HasColumnType("bit"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("UserExperiences"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Users", b => - { - b.HasBaseType("Microsoft.AspNetCore.Identity.IdentityUser"); - - b.Property("Bio") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("FirstName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("Headline") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("LastName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("ProfilePictureUrl") - .HasColumnType("nvarchar(max)"); - - b.HasDiscriminator().HasValue("Users"); - }); - - modelBuilder.Entity("CompaniesUsers", b => - { - b.HasOne("SkillProof.Entities.Models.Companies", null) - .WithMany() - .HasForeignKey("CompaniesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("SkillProof.Entities.Models.Users", null) - .WithMany() - .HasForeignKey("UsersId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.CodeCompletionQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("CodeCompletionQuestion") - .HasForeignKey("SkillProof.Entities.Models.CodeCompletionQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.FillInTheBlankQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("FillInTheBlankQuestions") - .HasForeignKey("SkillProof.Entities.Models.FillInTheBlankQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.JobApplications", b => - { - b.HasOne("SkillProof.Entities.Models.Jobs", "Job") - .WithMany("JobApplications") - .HasForeignKey("JobId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("SkillProof.Entities.Models.Tests", "Test") - .WithOne("JobApplication") - .HasForeignKey("SkillProof.Entities.Models.JobApplications", "TestId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("SkillProof.Entities.Models.Users", "User") - .WithMany("JobApplications") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Job"); - - b.Navigation("Test"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Jobs", b => - { - b.HasOne("SkillProof.Entities.Models.Companies", "Company") - .WithMany("Jobs") - .HasForeignKey("CompanyId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Company"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.MultipleChoiceQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("MultipleChoiceQuestion") - .HasForeignKey("SkillProof.Entities.Models.MultipleChoiceQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TestAnswers", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithMany("TestAnswers") - .HasForeignKey("QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("SkillProof.Entities.Models.Tests", "Test") - .WithMany("TestAnswers") - .HasForeignKey("TestId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - - b.Navigation("Test"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => - { - b.HasOne("SkillProof.Entities.Models.Users", "User") - .WithMany("Tests") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("User"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.UserExperiences", b => - { - b.HasOne("SkillProof.Entities.Models.Users", "User") - .WithMany("UserExperiences") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Companies", b => - { - b.Navigation("Jobs"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Jobs", b => - { - b.Navigation("JobApplications"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Questions", b => - { - b.Navigation("CodeCompletionQuestion"); - - b.Navigation("FillInTheBlankQuestions"); - - b.Navigation("MultipleChoiceQuestion"); - - b.Navigation("TestAnswers"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => - { - b.Navigation("JobApplication"); - - b.Navigation("TestAnswers"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Users", b => - { - b.Navigation("JobApplications"); - - b.Navigation("Tests"); - - b.Navigation("UserExperiences"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/SkillProof/SkillProof.Data/Migrations/20260319204453_AddQuestionBankQuestionTypes.Designer.cs b/backend/SkillProof/SkillProof.Data/Migrations/20260319204453_AddQuestionBankQuestionTypes.Designer.cs deleted file mode 100644 index 9651d3c..0000000 --- a/backend/SkillProof/SkillProof.Data/Migrations/20260319204453_AddQuestionBankQuestionTypes.Designer.cs +++ /dev/null @@ -1,829 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using SkillProof.Data; - -#nullable disable - -namespace SkillProof.Data.Migrations -{ - [DbContext(typeof(SkillProofDbContext))] - [Migration("20260319204453_AddQuestionBankQuestionTypes")] - partial class AddQuestionBankQuestionTypes - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.0") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - - modelBuilder.Entity("CompaniesUsers", b => - { - b.Property("CompaniesId") - .HasColumnType("nvarchar(450)"); - - b.Property("UsersId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("CompaniesId", "UsersId"); - - b.HasIndex("UsersId"); - - b.ToTable("CompaniesUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Discriminator") - .IsRequired() - .HasMaxLength(13) - .HasColumnType("nvarchar(13)"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers", (string)null); - - b.HasDiscriminator("Discriminator").HasValue("IdentityUser"); - - b.UseTphMappingStrategy(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", (string)null); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.CodeCompletionQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("AcceptedAnswers") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("CodeSnippet") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("CodeCompletionQuestions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Companies", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Website") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Companies"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.FillInTheBlankQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("Answer") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("manualFeedback") - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("FillInTheBlankQuestions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.JobApplications", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AppliedAt") - .HasColumnType("datetime2"); - - b.Property("JobId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("Status") - .HasColumnType("int"); - - b.Property("TestId") - .HasColumnType("nvarchar(450)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("JobId"); - - b.HasIndex("TestId") - .IsUnique() - .HasFilter("[TestId] IS NOT NULL"); - - b.HasIndex("UserId"); - - b.ToTable("JobApplications"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Jobs", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CompanyId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("EmploymentType") - .HasColumnType("int"); - - b.Property("Location") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Tags") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.HasIndex("CompanyId"); - - b.ToTable("Jobs"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.MultipleChoiceQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("AllowMultipleSelection") - .HasColumnType("bit"); - - b.Property("CorrectAnswerIds") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Options") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("MultipleChoiceQuestion"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Questions", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Difficulty") - .HasColumnType("int"); - - b.Property("IsActive") - .HasColumnType("bit"); - - b.Property("Language") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("QuestionText") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("nvarchar(255)"); - - b.Property("Type") - .HasColumnType("int"); - - b.Property("UpdatedAt") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.ToTable("Questions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TestAnswers", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AiFeedback") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("FreeTextResponse") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("IsCorrect") - .HasColumnType("bit"); - - b.Property("QuestionId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("TestId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("QuestionId"); - - b.HasIndex("TestId"); - - b.ToTable("TestAnswers"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CompletedAt") - .HasColumnType("datetime2"); - - b.Property("DifficultyLevel") - .HasColumnType("int"); - - b.Property("Passed") - .HasColumnType("bit"); - - b.Property("Score") - .HasColumnType("int"); - - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("Tests"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TrueFalseQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("CorrectAnswer") - .HasColumnType("bit"); - - b.Property("Explanation") - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("TrueFalseQuestions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.UserExperiences", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CompanyName") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("EndDate") - .HasColumnType("datetime2"); - - b.Property("JobTitle") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("StartDate") - .HasColumnType("datetime2"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("isVerified") - .HasColumnType("bit"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("UserExperiences"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Users", b => - { - b.HasBaseType("Microsoft.AspNetCore.Identity.IdentityUser"); - - b.Property("Bio") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("FirstName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("Headline") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("LastName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("ProfilePictureUrl") - .HasColumnType("nvarchar(max)"); - - b.HasDiscriminator().HasValue("Users"); - }); - - modelBuilder.Entity("CompaniesUsers", b => - { - b.HasOne("SkillProof.Entities.Models.Companies", null) - .WithMany() - .HasForeignKey("CompaniesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("SkillProof.Entities.Models.Users", null) - .WithMany() - .HasForeignKey("UsersId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.CodeCompletionQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("CodeCompletionQuestion") - .HasForeignKey("SkillProof.Entities.Models.CodeCompletionQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.FillInTheBlankQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("FillInTheBlankQuestions") - .HasForeignKey("SkillProof.Entities.Models.FillInTheBlankQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.JobApplications", b => - { - b.HasOne("SkillProof.Entities.Models.Jobs", "Job") - .WithMany("JobApplications") - .HasForeignKey("JobId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("SkillProof.Entities.Models.Tests", "Test") - .WithOne("JobApplication") - .HasForeignKey("SkillProof.Entities.Models.JobApplications", "TestId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("SkillProof.Entities.Models.Users", "User") - .WithMany("JobApplications") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Job"); - - b.Navigation("Test"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Jobs", b => - { - b.HasOne("SkillProof.Entities.Models.Companies", "Company") - .WithMany("Jobs") - .HasForeignKey("CompanyId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Company"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.MultipleChoiceQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("MultipleChoiceQuestion") - .HasForeignKey("SkillProof.Entities.Models.MultipleChoiceQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TestAnswers", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithMany("TestAnswers") - .HasForeignKey("QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("SkillProof.Entities.Models.Tests", "Test") - .WithMany("TestAnswers") - .HasForeignKey("TestId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - - b.Navigation("Test"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => - { - b.HasOne("SkillProof.Entities.Models.Users", "User") - .WithMany("Tests") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("User"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TrueFalseQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("TrueFalseQuestion") - .HasForeignKey("SkillProof.Entities.Models.TrueFalseQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.UserExperiences", b => - { - b.HasOne("SkillProof.Entities.Models.Users", "User") - .WithMany("UserExperiences") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Companies", b => - { - b.Navigation("Jobs"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Jobs", b => - { - b.Navigation("JobApplications"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Questions", b => - { - b.Navigation("CodeCompletionQuestion"); - - b.Navigation("FillInTheBlankQuestions"); - - b.Navigation("MultipleChoiceQuestion"); - - b.Navigation("TestAnswers"); - - b.Navigation("TrueFalseQuestion"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => - { - b.Navigation("JobApplication"); - - b.Navigation("TestAnswers"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Users", b => - { - b.Navigation("JobApplications"); - - b.Navigation("Tests"); - - b.Navigation("UserExperiences"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/SkillProof/SkillProof.Data/Migrations/20260319204453_AddQuestionBankQuestionTypes.cs b/backend/SkillProof/SkillProof.Data/Migrations/20260319204453_AddQuestionBankQuestionTypes.cs deleted file mode 100644 index 1a2d795..0000000 --- a/backend/SkillProof/SkillProof.Data/Migrations/20260319204453_AddQuestionBankQuestionTypes.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace SkillProof.Data.Migrations -{ - /// - public partial class AddQuestionBankQuestionTypes : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "TrueFalseQuestions", - columns: table => new - { - QuestionId = table.Column(type: "nvarchar(450)", nullable: false), - CorrectAnswer = table.Column(type: "bit", nullable: false), - Explanation = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_TrueFalseQuestions", x => x.QuestionId); - table.ForeignKey( - name: "FK_TrueFalseQuestions_Questions_QuestionId", - column: x => x.QuestionId, - principalTable: "Questions", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "TrueFalseQuestions"); - } - } -} diff --git a/backend/SkillProof/SkillProof.Data/Migrations/20260319214710_update1.Designer.cs b/backend/SkillProof/SkillProof.Data/Migrations/20260319214710_update1.Designer.cs deleted file mode 100644 index 6968563..0000000 --- a/backend/SkillProof/SkillProof.Data/Migrations/20260319214710_update1.Designer.cs +++ /dev/null @@ -1,801 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using SkillProof.Data; - -#nullable disable - -namespace SkillProof.Data.Migrations -{ - [DbContext(typeof(SkillProofDbContext))] - [Migration("20260319214710_update1")] - partial class update1 - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.0") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - - modelBuilder.Entity("CompaniesUsers", b => - { - b.Property("CompaniesId") - .HasColumnType("nvarchar(450)"); - - b.Property("UsersId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("CompaniesId", "UsersId"); - - b.HasIndex("UsersId"); - - b.ToTable("CompaniesUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Discriminator") - .IsRequired() - .HasMaxLength(13) - .HasColumnType("nvarchar(13)"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers", (string)null); - - b.HasDiscriminator().HasValue("IdentityUser"); - - b.UseTphMappingStrategy(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", (string)null); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.CodeCompletionQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("AcceptedAnswers") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("CodeSnippet") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("CodeCompletionQuestions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Companies", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Website") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Companies"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.FillInTheBlankQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("Answer") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("manualFeedback") - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("FillInTheBlankQuestions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.JobApplications", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AppliedAt") - .HasColumnType("datetime2"); - - b.Property("JobId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("Status") - .HasColumnType("int"); - - b.Property("TestId") - .HasColumnType("nvarchar(450)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("JobId"); - - b.HasIndex("TestId") - .IsUnique() - .HasFilter("[TestId] IS NOT NULL"); - - b.HasIndex("UserId"); - - b.ToTable("JobApplications"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Jobs", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CompanyId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("EmploymentType") - .HasColumnType("int"); - - b.Property("Location") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Tags") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.HasIndex("CompanyId"); - - b.ToTable("Jobs"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.MultipleChoiceQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("AllowMultipleSelection") - .HasColumnType("bit"); - - b.Property("CorrectAnswerIds") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Options") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("MultipleChoiceQuestion"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Questions", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Difficulty") - .HasColumnType("int"); - - b.Property("IsActive") - .HasColumnType("bit"); - - b.Property("Language") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("QuestionText") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("nvarchar(255)"); - - b.Property("Type") - .HasColumnType("int"); - - b.Property("UpdatedAt") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.ToTable("Questions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TestAnswers", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AiFeedback") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("FreeTextResponse") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("IsCorrect") - .HasColumnType("bit"); - - b.Property("QuestionId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("TestId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("QuestionId"); - - b.HasIndex("TestId"); - - b.ToTable("TestAnswers"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CompletedAt") - .HasColumnType("datetime2"); - - b.Property("DifficultyLevel") - .HasColumnType("int"); - - b.Property("Passed") - .HasColumnType("bit"); - - b.Property("Score") - .HasColumnType("int"); - - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("Tests"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.UserExperiences", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CompanyName") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("EndDate") - .HasColumnType("datetime2"); - - b.Property("JobTitle") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("StartDate") - .HasColumnType("datetime2"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("isVerified") - .HasColumnType("bit"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("UserExperiences"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Users", b => - { - b.HasBaseType("Microsoft.AspNetCore.Identity.IdentityUser"); - - b.Property("Bio") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("FirstName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("Headline") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("LastName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("ProfilePicture") - .IsRequired() - .HasColumnType("varbinary(max)"); - - b.HasDiscriminator().HasValue("Users"); - }); - - modelBuilder.Entity("CompaniesUsers", b => - { - b.HasOne("SkillProof.Entities.Models.Companies", null) - .WithMany() - .HasForeignKey("CompaniesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("SkillProof.Entities.Models.Users", null) - .WithMany() - .HasForeignKey("UsersId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.CodeCompletionQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("CodeCompletionQuestion") - .HasForeignKey("SkillProof.Entities.Models.CodeCompletionQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.FillInTheBlankQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("FillInTheBlankQuestions") - .HasForeignKey("SkillProof.Entities.Models.FillInTheBlankQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.JobApplications", b => - { - b.HasOne("SkillProof.Entities.Models.Jobs", "Job") - .WithMany("JobApplications") - .HasForeignKey("JobId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("SkillProof.Entities.Models.Tests", "Test") - .WithOne("JobApplication") - .HasForeignKey("SkillProof.Entities.Models.JobApplications", "TestId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("SkillProof.Entities.Models.Users", "User") - .WithMany("JobApplications") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Job"); - - b.Navigation("Test"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Jobs", b => - { - b.HasOne("SkillProof.Entities.Models.Companies", "Company") - .WithMany("Jobs") - .HasForeignKey("CompanyId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Company"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.MultipleChoiceQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("MultipleChoiceQuestion") - .HasForeignKey("SkillProof.Entities.Models.MultipleChoiceQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TestAnswers", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithMany("TestAnswers") - .HasForeignKey("QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("SkillProof.Entities.Models.Tests", "Test") - .WithMany("TestAnswers") - .HasForeignKey("TestId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - - b.Navigation("Test"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => - { - b.HasOne("SkillProof.Entities.Models.Users", "User") - .WithMany("Tests") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("User"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.UserExperiences", b => - { - b.HasOne("SkillProof.Entities.Models.Users", "User") - .WithMany("UserExperiences") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Companies", b => - { - b.Navigation("Jobs"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Jobs", b => - { - b.Navigation("JobApplications"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Questions", b => - { - b.Navigation("CodeCompletionQuestion"); - - b.Navigation("FillInTheBlankQuestions"); - - b.Navigation("MultipleChoiceQuestion"); - - b.Navigation("TestAnswers"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => - { - b.Navigation("JobApplication"); - - b.Navigation("TestAnswers"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Users", b => - { - b.Navigation("JobApplications"); - - b.Navigation("Tests"); - - b.Navigation("UserExperiences"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/SkillProof/SkillProof.Data/Migrations/20260319214710_update1.cs b/backend/SkillProof/SkillProof.Data/Migrations/20260319214710_update1.cs deleted file mode 100644 index 5c7888d..0000000 --- a/backend/SkillProof/SkillProof.Data/Migrations/20260319214710_update1.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace SkillProof.Data.Migrations -{ - /// - public partial class update1 : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "ProfilePictureUrl", - table: "AspNetUsers"); - - migrationBuilder.AddColumn( - name: "ProfilePicture", - table: "AspNetUsers", - type: "varbinary(max)", - nullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "ProfilePicture", - table: "AspNetUsers"); - - migrationBuilder.AddColumn( - name: "ProfilePictureUrl", - table: "AspNetUsers", - type: "nvarchar(max)", - nullable: true); - } - } -} diff --git a/backend/SkillProof/SkillProof.Data/Migrations/20260322091850_RenameJobsToJob.Designer.cs b/backend/SkillProof/SkillProof.Data/Migrations/20260322091850_RenameJobsToJob.Designer.cs deleted file mode 100644 index 25d1a4d..0000000 --- a/backend/SkillProof/SkillProof.Data/Migrations/20260322091850_RenameJobsToJob.Designer.cs +++ /dev/null @@ -1,830 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using SkillProof.Data; - -#nullable disable - -namespace SkillProof.Data.Migrations -{ - [DbContext(typeof(SkillProofDbContext))] - [Migration("20260322091850_RenameJobsToJob")] - partial class RenameJobsToJob - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.0") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - - modelBuilder.Entity("CompaniesUsers", b => - { - b.Property("CompaniesId") - .HasColumnType("nvarchar(450)"); - - b.Property("UsersId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("CompaniesId", "UsersId"); - - b.HasIndex("UsersId"); - - b.ToTable("CompaniesUsers"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Discriminator") - .IsRequired() - .HasMaxLength(13) - .HasColumnType("nvarchar(13)"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers", (string)null); - - b.HasDiscriminator().HasValue("IdentityUser"); - - b.UseTphMappingStrategy(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", (string)null); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.CodeCompletionQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("AcceptedAnswers") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("CodeSnippet") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("CodeCompletionQuestions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Companies", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Website") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Companies"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.FillInTheBlankQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("Answer") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("manualFeedback") - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("FillInTheBlankQuestions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Job", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CompanyId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("EmploymentType") - .HasColumnType("int"); - - b.Property("Location") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Tags") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.HasIndex("CompanyId"); - - b.ToTable("Jobs"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.JobApplication", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AppliedAt") - .HasColumnType("datetime2"); - - b.Property("JobId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("Status") - .HasColumnType("int"); - - b.Property("TestId") - .HasColumnType("nvarchar(450)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("JobId"); - - b.HasIndex("TestId") - .IsUnique() - .HasFilter("[TestId] IS NOT NULL"); - - b.HasIndex("UserId"); - - b.ToTable("JobApplications"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.MultipleChoiceQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("AllowMultipleSelection") - .HasColumnType("bit"); - - b.Property("CorrectAnswerIds") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Options") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("MultipleChoiceQuestion"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Questions", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Difficulty") - .HasColumnType("int"); - - b.Property("IsActive") - .HasColumnType("bit"); - - b.Property("Language") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("QuestionText") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("nvarchar(255)"); - - b.Property("Type") - .HasColumnType("int"); - - b.Property("UpdatedAt") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.ToTable("Questions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TestAnswers", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AiFeedback") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("FreeTextResponse") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("IsCorrect") - .HasColumnType("bit"); - - b.Property("QuestionId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("TestId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("QuestionId"); - - b.HasIndex("TestId"); - - b.ToTable("TestAnswers"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CompletedAt") - .HasColumnType("datetime2"); - - b.Property("DifficultyLevel") - .HasColumnType("int"); - - b.Property("Passed") - .HasColumnType("bit"); - - b.Property("Score") - .HasColumnType("int"); - - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("Tests"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TrueFalseQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("CorrectAnswer") - .HasColumnType("bit"); - - b.Property("Explanation") - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("TrueFalseQuestions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.UserExperiences", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CompanyName") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("EndDate") - .HasColumnType("datetime2"); - - b.Property("JobTitle") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("StartDate") - .HasColumnType("datetime2"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("isVerified") - .HasColumnType("bit"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("UserExperiences"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Users", b => - { - b.HasBaseType("Microsoft.AspNetCore.Identity.IdentityUser"); - - b.Property("Bio") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("FirstName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("Headline") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("LastName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("ProfilePicture") - .IsRequired() - .HasColumnType("varbinary(max)"); - - b.HasDiscriminator().HasValue("Users"); - }); - - modelBuilder.Entity("CompaniesUsers", b => - { - b.HasOne("SkillProof.Entities.Models.Companies", null) - .WithMany() - .HasForeignKey("CompaniesId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("SkillProof.Entities.Models.Users", null) - .WithMany() - .HasForeignKey("UsersId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.CodeCompletionQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("CodeCompletionQuestion") - .HasForeignKey("SkillProof.Entities.Models.CodeCompletionQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.FillInTheBlankQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("FillInTheBlankQuestions") - .HasForeignKey("SkillProof.Entities.Models.FillInTheBlankQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Job", b => - { - b.HasOne("SkillProof.Entities.Models.Companies", "Company") - .WithMany("Jobs") - .HasForeignKey("CompanyId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Company"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.JobApplication", b => - { - b.HasOne("SkillProof.Entities.Models.Job", "Job") - .WithMany("JobApplications") - .HasForeignKey("JobId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("SkillProof.Entities.Models.Tests", "Test") - .WithOne("JobApplication") - .HasForeignKey("SkillProof.Entities.Models.JobApplication", "TestId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("SkillProof.Entities.Models.Users", "User") - .WithMany("JobApplications") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Job"); - - b.Navigation("Test"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.MultipleChoiceQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("MultipleChoiceQuestion") - .HasForeignKey("SkillProof.Entities.Models.MultipleChoiceQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TestAnswers", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithMany("TestAnswers") - .HasForeignKey("QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("SkillProof.Entities.Models.Tests", "Test") - .WithMany("TestAnswers") - .HasForeignKey("TestId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - - b.Navigation("Test"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => - { - b.HasOne("SkillProof.Entities.Models.Users", "User") - .WithMany("Tests") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("User"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TrueFalseQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("TrueFalseQuestion") - .HasForeignKey("SkillProof.Entities.Models.TrueFalseQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.UserExperiences", b => - { - b.HasOne("SkillProof.Entities.Models.Users", "User") - .WithMany("UserExperiences") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Companies", b => - { - b.Navigation("Jobs"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Job", b => - { - b.Navigation("JobApplications"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Questions", b => - { - b.Navigation("CodeCompletionQuestion"); - - b.Navigation("FillInTheBlankQuestions"); - - b.Navigation("MultipleChoiceQuestion"); - - b.Navigation("TestAnswers"); - - b.Navigation("TrueFalseQuestion"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => - { - b.Navigation("JobApplication"); - - b.Navigation("TestAnswers"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Users", b => - { - b.Navigation("JobApplications"); - - b.Navigation("Tests"); - - b.Navigation("UserExperiences"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/SkillProof/SkillProof.Data/Migrations/20260322091850_RenameJobsToJob.cs b/backend/SkillProof/SkillProof.Data/Migrations/20260322091850_RenameJobsToJob.cs deleted file mode 100644 index af761c9..0000000 --- a/backend/SkillProof/SkillProof.Data/Migrations/20260322091850_RenameJobsToJob.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace SkillProof.Data.Migrations -{ - /// - public partial class RenameJobsToJob : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - - } - } -} diff --git a/backend/SkillProof/SkillProof.Data/Migrations/20260401084619_FixCompanyUserRelation.Designer.cs b/backend/SkillProof/SkillProof.Data/Migrations/20260401084619_FixCompanyUserRelation.Designer.cs deleted file mode 100644 index beeb771..0000000 --- a/backend/SkillProof/SkillProof.Data/Migrations/20260401084619_FixCompanyUserRelation.Designer.cs +++ /dev/null @@ -1,817 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using SkillProof.Data; - -#nullable disable - -namespace SkillProof.Data.Migrations -{ - [DbContext(typeof(SkillProofDbContext))] - [Migration("20260401084619_FixCompanyUserRelation")] - partial class FixCompanyUserRelation - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.0") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Discriminator") - .IsRequired() - .HasMaxLength(13) - .HasColumnType("nvarchar(13)"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers", (string)null); - - b.HasDiscriminator().HasValue("IdentityUser"); - - b.UseTphMappingStrategy(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", (string)null); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.CodeCompletionQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("AcceptedAnswers") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("CodeSnippet") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("CodeCompletionQuestions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Companies", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Website") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Companies"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.FillInTheBlankQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("Answer") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("manualFeedback") - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("FillInTheBlankQuestions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Job", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CompanyId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("EmploymentType") - .HasColumnType("int"); - - b.Property("Location") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Tags") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.HasIndex("CompanyId"); - - b.ToTable("Jobs"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.JobApplication", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AppliedAt") - .HasColumnType("datetime2"); - - b.Property("JobId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("Status") - .HasColumnType("int"); - - b.Property("TestId") - .HasColumnType("nvarchar(450)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("JobId"); - - b.HasIndex("TestId") - .IsUnique() - .HasFilter("[TestId] IS NOT NULL"); - - b.HasIndex("UserId"); - - b.ToTable("JobApplications"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.MultipleChoiceQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("AllowMultipleSelection") - .HasColumnType("bit"); - - b.Property("CorrectAnswerIds") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Options") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("MultipleChoiceQuestion"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Questions", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Difficulty") - .HasColumnType("int"); - - b.Property("IsActive") - .HasColumnType("bit"); - - b.Property("Language") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("QuestionText") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("nvarchar(255)"); - - b.Property("Type") - .HasColumnType("int"); - - b.Property("UpdatedAt") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.ToTable("Questions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TestAnswers", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AiFeedback") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("FreeTextResponse") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("IsCorrect") - .HasColumnType("bit"); - - b.Property("QuestionId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("TestId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("QuestionId"); - - b.HasIndex("TestId"); - - b.ToTable("TestAnswers"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CompletedAt") - .HasColumnType("datetime2"); - - b.Property("DifficultyLevel") - .HasColumnType("int"); - - b.Property("Passed") - .HasColumnType("bit"); - - b.Property("Score") - .HasColumnType("int"); - - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("Tests"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TrueFalseQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("CorrectAnswer") - .HasColumnType("bit"); - - b.Property("Explanation") - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("TrueFalseQuestions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.UserExperiences", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CompanyName") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("EndDate") - .HasColumnType("datetime2"); - - b.Property("JobTitle") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("StartDate") - .HasColumnType("datetime2"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("isVerified") - .HasColumnType("bit"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("UserExperiences"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Users", b => - { - b.HasBaseType("Microsoft.AspNetCore.Identity.IdentityUser"); - - b.Property("Bio") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("CompanyId") - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("FirstName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("Headline") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("LastName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("ProfilePicture") - .IsRequired() - .HasColumnType("varbinary(max)"); - - b.HasIndex("CompanyId"); - - b.HasDiscriminator().HasValue("Users"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.CodeCompletionQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("CodeCompletionQuestion") - .HasForeignKey("SkillProof.Entities.Models.CodeCompletionQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.FillInTheBlankQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("FillInTheBlankQuestions") - .HasForeignKey("SkillProof.Entities.Models.FillInTheBlankQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Job", b => - { - b.HasOne("SkillProof.Entities.Models.Companies", "Company") - .WithMany("Jobs") - .HasForeignKey("CompanyId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Company"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.JobApplication", b => - { - b.HasOne("SkillProof.Entities.Models.Job", "Job") - .WithMany("JobApplications") - .HasForeignKey("JobId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("SkillProof.Entities.Models.Tests", "Test") - .WithOne("JobApplication") - .HasForeignKey("SkillProof.Entities.Models.JobApplication", "TestId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("SkillProof.Entities.Models.Users", "User") - .WithMany("JobApplications") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Job"); - - b.Navigation("Test"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.MultipleChoiceQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("MultipleChoiceQuestion") - .HasForeignKey("SkillProof.Entities.Models.MultipleChoiceQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TestAnswers", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithMany("TestAnswers") - .HasForeignKey("QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("SkillProof.Entities.Models.Tests", "Test") - .WithMany("TestAnswers") - .HasForeignKey("TestId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - - b.Navigation("Test"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => - { - b.HasOne("SkillProof.Entities.Models.Users", "User") - .WithMany("Tests") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("User"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TrueFalseQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("TrueFalseQuestion") - .HasForeignKey("SkillProof.Entities.Models.TrueFalseQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.UserExperiences", b => - { - b.HasOne("SkillProof.Entities.Models.Users", "User") - .WithMany("UserExperiences") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Users", b => - { - b.HasOne("SkillProof.Entities.Models.Companies", "Companies") - .WithMany("Users") - .HasForeignKey("CompanyId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Companies"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Companies", b => - { - b.Navigation("Jobs"); - - b.Navigation("Users"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Job", b => - { - b.Navigation("JobApplications"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Questions", b => - { - b.Navigation("CodeCompletionQuestion"); - - b.Navigation("FillInTheBlankQuestions"); - - b.Navigation("MultipleChoiceQuestion"); - - b.Navigation("TestAnswers"); - - b.Navigation("TrueFalseQuestion"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => - { - b.Navigation("JobApplication"); - - b.Navigation("TestAnswers"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Users", b => - { - b.Navigation("JobApplications"); - - b.Navigation("Tests"); - - b.Navigation("UserExperiences"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/SkillProof/SkillProof.Data/Migrations/20260401084619_FixCompanyUserRelation.cs b/backend/SkillProof/SkillProof.Data/Migrations/20260401084619_FixCompanyUserRelation.cs deleted file mode 100644 index c517751..0000000 --- a/backend/SkillProof/SkillProof.Data/Migrations/20260401084619_FixCompanyUserRelation.cs +++ /dev/null @@ -1,81 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace SkillProof.Data.Migrations -{ - /// - public partial class FixCompanyUserRelation : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "CompaniesUsers"); - - migrationBuilder.AddColumn( - name: "CompanyId", - table: "AspNetUsers", - type: "nvarchar(450)", - nullable: true); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUsers_CompanyId", - table: "AspNetUsers", - column: "CompanyId"); - - migrationBuilder.AddForeignKey( - name: "FK_AspNetUsers_Companies_CompanyId", - table: "AspNetUsers", - column: "CompanyId", - principalTable: "Companies", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_AspNetUsers_Companies_CompanyId", - table: "AspNetUsers"); - - migrationBuilder.DropIndex( - name: "IX_AspNetUsers_CompanyId", - table: "AspNetUsers"); - - migrationBuilder.DropColumn( - name: "CompanyId", - table: "AspNetUsers"); - - migrationBuilder.CreateTable( - name: "CompaniesUsers", - columns: table => new - { - CompaniesId = table.Column(type: "nvarchar(450)", nullable: false), - UsersId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CompaniesUsers", x => new { x.CompaniesId, x.UsersId }); - table.ForeignKey( - name: "FK_CompaniesUsers_AspNetUsers_UsersId", - column: x => x.UsersId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_CompaniesUsers_Companies_CompaniesId", - column: x => x.CompaniesId, - principalTable: "Companies", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_CompaniesUsers_UsersId", - table: "CompaniesUsers", - column: "UsersId"); - } - } -} diff --git a/backend/SkillProof/SkillProof.Data/Migrations/20260401090725_AddCompanyRoleToUser.Designer.cs b/backend/SkillProof/SkillProof.Data/Migrations/20260401090725_AddCompanyRoleToUser.Designer.cs deleted file mode 100644 index a353de0..0000000 --- a/backend/SkillProof/SkillProof.Data/Migrations/20260401090725_AddCompanyRoleToUser.Designer.cs +++ /dev/null @@ -1,820 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using SkillProof.Data; - -#nullable disable - -namespace SkillProof.Data.Migrations -{ - [DbContext(typeof(SkillProofDbContext))] - [Migration("20260401090725_AddCompanyRoleToUser")] - partial class AddCompanyRoleToUser - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.0") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Discriminator") - .IsRequired() - .HasMaxLength(13) - .HasColumnType("nvarchar(13)"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers", (string)null); - - b.HasDiscriminator().HasValue("IdentityUser"); - - b.UseTphMappingStrategy(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", (string)null); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.CodeCompletionQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("AcceptedAnswers") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("CodeSnippet") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("CodeCompletionQuestions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Companies", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Website") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Companies"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.FillInTheBlankQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("Answer") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("manualFeedback") - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("FillInTheBlankQuestions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Job", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CompanyId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("EmploymentType") - .HasColumnType("int"); - - b.Property("Location") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Tags") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.HasIndex("CompanyId"); - - b.ToTable("Jobs"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.JobApplication", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AppliedAt") - .HasColumnType("datetime2"); - - b.Property("JobId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("Status") - .HasColumnType("int"); - - b.Property("TestId") - .HasColumnType("nvarchar(450)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("JobId"); - - b.HasIndex("TestId") - .IsUnique() - .HasFilter("[TestId] IS NOT NULL"); - - b.HasIndex("UserId"); - - b.ToTable("JobApplications"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.MultipleChoiceQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("AllowMultipleSelection") - .HasColumnType("bit"); - - b.Property("CorrectAnswerIds") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Options") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("MultipleChoiceQuestion"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Questions", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Difficulty") - .HasColumnType("int"); - - b.Property("IsActive") - .HasColumnType("bit"); - - b.Property("Language") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("QuestionText") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("nvarchar(255)"); - - b.Property("Type") - .HasColumnType("int"); - - b.Property("UpdatedAt") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.ToTable("Questions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TestAnswers", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AiFeedback") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("FreeTextResponse") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("IsCorrect") - .HasColumnType("bit"); - - b.Property("QuestionId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("TestId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("QuestionId"); - - b.HasIndex("TestId"); - - b.ToTable("TestAnswers"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CompletedAt") - .HasColumnType("datetime2"); - - b.Property("DifficultyLevel") - .HasColumnType("int"); - - b.Property("Passed") - .HasColumnType("bit"); - - b.Property("Score") - .HasColumnType("int"); - - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("Tests"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TrueFalseQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("CorrectAnswer") - .HasColumnType("bit"); - - b.Property("Explanation") - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("TrueFalseQuestions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.UserExperiences", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CompanyName") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("EndDate") - .HasColumnType("datetime2"); - - b.Property("JobTitle") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("StartDate") - .HasColumnType("datetime2"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("isVerified") - .HasColumnType("bit"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("UserExperiences"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Users", b => - { - b.HasBaseType("Microsoft.AspNetCore.Identity.IdentityUser"); - - b.Property("Bio") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("CompanyId") - .HasColumnType("nvarchar(450)"); - - b.Property("CompanyRole") - .HasColumnType("nvarchar(max)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("FirstName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("Headline") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("LastName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("ProfilePicture") - .IsRequired() - .HasColumnType("varbinary(max)"); - - b.HasIndex("CompanyId"); - - b.HasDiscriminator().HasValue("Users"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.CodeCompletionQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("CodeCompletionQuestion") - .HasForeignKey("SkillProof.Entities.Models.CodeCompletionQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.FillInTheBlankQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("FillInTheBlankQuestions") - .HasForeignKey("SkillProof.Entities.Models.FillInTheBlankQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Job", b => - { - b.HasOne("SkillProof.Entities.Models.Companies", "Company") - .WithMany("Jobs") - .HasForeignKey("CompanyId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Company"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.JobApplication", b => - { - b.HasOne("SkillProof.Entities.Models.Job", "Job") - .WithMany("JobApplications") - .HasForeignKey("JobId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("SkillProof.Entities.Models.Tests", "Test") - .WithOne("JobApplication") - .HasForeignKey("SkillProof.Entities.Models.JobApplication", "TestId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("SkillProof.Entities.Models.Users", "User") - .WithMany("JobApplications") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Job"); - - b.Navigation("Test"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.MultipleChoiceQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("MultipleChoiceQuestion") - .HasForeignKey("SkillProof.Entities.Models.MultipleChoiceQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TestAnswers", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithMany("TestAnswers") - .HasForeignKey("QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("SkillProof.Entities.Models.Tests", "Test") - .WithMany("TestAnswers") - .HasForeignKey("TestId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - - b.Navigation("Test"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => - { - b.HasOne("SkillProof.Entities.Models.Users", "User") - .WithMany("Tests") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("User"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TrueFalseQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("TrueFalseQuestion") - .HasForeignKey("SkillProof.Entities.Models.TrueFalseQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.UserExperiences", b => - { - b.HasOne("SkillProof.Entities.Models.Users", "User") - .WithMany("UserExperiences") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Users", b => - { - b.HasOne("SkillProof.Entities.Models.Companies", "Companies") - .WithMany("Users") - .HasForeignKey("CompanyId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Companies"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Companies", b => - { - b.Navigation("Jobs"); - - b.Navigation("Users"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Job", b => - { - b.Navigation("JobApplications"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Questions", b => - { - b.Navigation("CodeCompletionQuestion"); - - b.Navigation("FillInTheBlankQuestions"); - - b.Navigation("MultipleChoiceQuestion"); - - b.Navigation("TestAnswers"); - - b.Navigation("TrueFalseQuestion"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => - { - b.Navigation("JobApplication"); - - b.Navigation("TestAnswers"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Users", b => - { - b.Navigation("JobApplications"); - - b.Navigation("Tests"); - - b.Navigation("UserExperiences"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/SkillProof/SkillProof.Data/Migrations/20260401090725_AddCompanyRoleToUser.cs b/backend/SkillProof/SkillProof.Data/Migrations/20260401090725_AddCompanyRoleToUser.cs deleted file mode 100644 index 75257f6..0000000 --- a/backend/SkillProof/SkillProof.Data/Migrations/20260401090725_AddCompanyRoleToUser.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace SkillProof.Data.Migrations -{ - /// - public partial class AddCompanyRoleToUser : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "CompanyRole", - table: "AspNetUsers", - type: "nvarchar(max)", - nullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "CompanyRole", - table: "AspNetUsers"); - } - } -} diff --git a/backend/SkillProof/SkillProof.Data/Migrations/20260420201903_QuestionUpdate.Designer.cs b/backend/SkillProof/SkillProof.Data/Migrations/20260420201903_QuestionUpdate.Designer.cs deleted file mode 100644 index cdfb91b..0000000 --- a/backend/SkillProof/SkillProof.Data/Migrations/20260420201903_QuestionUpdate.Designer.cs +++ /dev/null @@ -1,824 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using SkillProof.Data; - -#nullable disable - -namespace SkillProof.Data.Migrations -{ - [DbContext(typeof(SkillProofDbContext))] - [Migration("20260420201903_QuestionUpdate")] - partial class QuestionUpdate - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.0") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Discriminator") - .IsRequired() - .HasMaxLength(13) - .HasColumnType("nvarchar(13)"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers", (string)null); - - b.HasDiscriminator().HasValue("IdentityUser"); - - b.UseTphMappingStrategy(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", (string)null); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.CodeCompletionQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("AcceptedAnswers") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("CodeSnippet") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("CodeCompletionQuestions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Companies", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Website") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Companies"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.FillInTheBlankQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("Answer") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("manualFeedback") - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("FillInTheBlankQuestions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Job", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CompanyId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("EmploymentType") - .HasColumnType("int"); - - b.Property("Location") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Tags") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.HasIndex("CompanyId"); - - b.ToTable("Jobs"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.JobApplication", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AppliedAt") - .HasColumnType("datetime2"); - - b.Property("JobId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("Status") - .HasColumnType("int"); - - b.Property("TestId") - .HasColumnType("nvarchar(450)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("JobId"); - - b.HasIndex("TestId") - .IsUnique() - .HasFilter("[TestId] IS NOT NULL"); - - b.HasIndex("UserId"); - - b.ToTable("JobApplications"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.MultipleChoiceQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("AllowMultipleSelection") - .HasColumnType("bit"); - - b.Property("CorrectAnswerIds") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Options") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("MultipleChoiceQuestion"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Questions", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Difficulty") - .HasColumnType("int"); - - b.Property("IsActive") - .HasColumnType("bit"); - - b.Property("Language") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("QuestionText") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.PrimitiveCollection("Tags") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("nvarchar(255)"); - - b.Property("Type") - .HasColumnType("int"); - - b.Property("UpdatedAt") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.ToTable("Questions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TestAnswers", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AiFeedback") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("FreeTextResponse") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("IsCorrect") - .HasColumnType("bit"); - - b.Property("QuestionId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("TestId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("QuestionId"); - - b.HasIndex("TestId"); - - b.ToTable("TestAnswers"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CompletedAt") - .HasColumnType("datetime2"); - - b.Property("DifficultyLevel") - .HasColumnType("int"); - - b.Property("Passed") - .HasColumnType("bit"); - - b.Property("Score") - .HasColumnType("int"); - - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("Tests"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TrueFalseQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("CorrectAnswer") - .HasColumnType("bit"); - - b.Property("Explanation") - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("TrueFalseQuestions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.UserExperiences", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CompanyName") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("EndDate") - .HasColumnType("datetime2"); - - b.Property("JobTitle") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("StartDate") - .HasColumnType("datetime2"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("isVerified") - .HasColumnType("bit"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("UserExperiences"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Users", b => - { - b.HasBaseType("Microsoft.AspNetCore.Identity.IdentityUser"); - - b.Property("Bio") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("CompanyId") - .HasColumnType("nvarchar(450)"); - - b.Property("CompanyRole") - .HasColumnType("nvarchar(max)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("FirstName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("Headline") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("LastName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("ProfilePicture") - .IsRequired() - .HasColumnType("varbinary(max)"); - - b.HasIndex("CompanyId"); - - b.HasDiscriminator().HasValue("Users"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.CodeCompletionQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("CodeCompletionQuestion") - .HasForeignKey("SkillProof.Entities.Models.CodeCompletionQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.FillInTheBlankQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("FillInTheBlankQuestions") - .HasForeignKey("SkillProof.Entities.Models.FillInTheBlankQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Job", b => - { - b.HasOne("SkillProof.Entities.Models.Companies", "Company") - .WithMany("Jobs") - .HasForeignKey("CompanyId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Company"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.JobApplication", b => - { - b.HasOne("SkillProof.Entities.Models.Job", "Job") - .WithMany("JobApplications") - .HasForeignKey("JobId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("SkillProof.Entities.Models.Tests", "Test") - .WithOne("JobApplication") - .HasForeignKey("SkillProof.Entities.Models.JobApplication", "TestId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("SkillProof.Entities.Models.Users", "User") - .WithMany("JobApplications") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Job"); - - b.Navigation("Test"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.MultipleChoiceQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("MultipleChoiceQuestion") - .HasForeignKey("SkillProof.Entities.Models.MultipleChoiceQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TestAnswers", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithMany("TestAnswers") - .HasForeignKey("QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("SkillProof.Entities.Models.Tests", "Test") - .WithMany("TestAnswers") - .HasForeignKey("TestId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - - b.Navigation("Test"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => - { - b.HasOne("SkillProof.Entities.Models.Users", "User") - .WithMany("Tests") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("User"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TrueFalseQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("TrueFalseQuestion") - .HasForeignKey("SkillProof.Entities.Models.TrueFalseQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.UserExperiences", b => - { - b.HasOne("SkillProof.Entities.Models.Users", "User") - .WithMany("UserExperiences") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Users", b => - { - b.HasOne("SkillProof.Entities.Models.Companies", "Companies") - .WithMany("Users") - .HasForeignKey("CompanyId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Companies"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Companies", b => - { - b.Navigation("Jobs"); - - b.Navigation("Users"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Job", b => - { - b.Navigation("JobApplications"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Questions", b => - { - b.Navigation("CodeCompletionQuestion"); - - b.Navigation("FillInTheBlankQuestions"); - - b.Navigation("MultipleChoiceQuestion"); - - b.Navigation("TestAnswers"); - - b.Navigation("TrueFalseQuestion"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => - { - b.Navigation("JobApplication"); - - b.Navigation("TestAnswers"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Users", b => - { - b.Navigation("JobApplications"); - - b.Navigation("Tests"); - - b.Navigation("UserExperiences"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/SkillProof/SkillProof.Data/Migrations/20260420201903_QuestionUpdate.cs b/backend/SkillProof/SkillProof.Data/Migrations/20260420201903_QuestionUpdate.cs deleted file mode 100644 index ccafd6e..0000000 --- a/backend/SkillProof/SkillProof.Data/Migrations/20260420201903_QuestionUpdate.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace SkillProof.Data.Migrations -{ - /// - public partial class QuestionUpdate : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "Tags", - table: "Questions", - type: "nvarchar(max)", - nullable: false, - defaultValue: "[]"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "Tags", - table: "Questions"); - } - } -} diff --git a/backend/SkillProof/SkillProof.Data/Migrations/20260421091623_AddJobQuestions.Designer.cs b/backend/SkillProof/SkillProof.Data/Migrations/20260421091623_AddJobQuestions.Designer.cs deleted file mode 100644 index 8534c75..0000000 --- a/backend/SkillProof/SkillProof.Data/Migrations/20260421091623_AddJobQuestions.Designer.cs +++ /dev/null @@ -1,850 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using SkillProof.Data; - -#nullable disable - -namespace SkillProof.Data.Migrations -{ - [DbContext(typeof(SkillProofDbContext))] - [Migration("20260421091623_AddJobQuestions")] - partial class AddJobQuestions - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.0") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - - modelBuilder.Entity("JobQuestions", b => - { - b.Property("JobsId") - .HasColumnType("nvarchar(450)"); - - b.Property("QuestionsId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("JobsId", "QuestionsId"); - - b.HasIndex("QuestionsId"); - - b.ToTable("JobQuestions"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex") - .HasFilter("[NormalizedName] IS NOT NULL"); - - b.ToTable("AspNetRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AccessFailedCount") - .HasColumnType("int"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("nvarchar(max)"); - - b.Property("Discriminator") - .IsRequired() - .HasMaxLength(13) - .HasColumnType("nvarchar(13)"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("EmailConfirmed") - .HasColumnType("bit"); - - b.Property("LockoutEnabled") - .HasColumnType("bit"); - - b.Property("LockoutEnd") - .HasColumnType("datetimeoffset"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.Property("PasswordHash") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumber") - .HasColumnType("nvarchar(max)"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("bit"); - - b.Property("SecurityStamp") - .HasColumnType("nvarchar(max)"); - - b.Property("TwoFactorEnabled") - .HasColumnType("bit"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex") - .HasFilter("[NormalizedUserName] IS NOT NULL"); - - b.ToTable("AspNetUsers", (string)null); - - b.HasDiscriminator().HasValue("IdentityUser"); - - b.UseTphMappingStrategy(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("nvarchar(max)"); - - b.Property("ClaimValue") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderKey") - .HasColumnType("nvarchar(450)"); - - b.Property("ProviderDisplayName") - .HasColumnType("nvarchar(max)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("RoleId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.Property("LoginProvider") - .HasColumnType("nvarchar(450)"); - - b.Property("Name") - .HasColumnType("nvarchar(450)"); - - b.Property("Value") - .HasColumnType("nvarchar(max)"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", (string)null); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.CodeCompletionQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("AcceptedAnswers") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("CodeSnippet") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("CodeCompletionQuestions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Companies", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Website") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Companies"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.FillInTheBlankQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("Answer") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("manualFeedback") - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("FillInTheBlankQuestions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Job", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CompanyId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("EmploymentType") - .HasColumnType("int"); - - b.Property("Location") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Tags") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.HasIndex("CompanyId"); - - b.ToTable("Jobs"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.JobApplication", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AppliedAt") - .HasColumnType("datetime2"); - - b.Property("JobId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("Status") - .HasColumnType("int"); - - b.Property("TestId") - .HasColumnType("nvarchar(450)"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("JobId"); - - b.HasIndex("TestId") - .IsUnique() - .HasFilter("[TestId] IS NOT NULL"); - - b.HasIndex("UserId"); - - b.ToTable("JobApplications"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.MultipleChoiceQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("AllowMultipleSelection") - .HasColumnType("bit"); - - b.Property("CorrectAnswerIds") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Options") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("MultipleChoiceQuestion"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Questions", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Difficulty") - .HasColumnType("int"); - - b.Property("IsActive") - .HasColumnType("bit"); - - b.Property("Language") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("QuestionText") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("nvarchar(255)"); - - b.Property("Type") - .HasColumnType("int"); - - b.Property("UpdatedAt") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.ToTable("Questions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TestAnswers", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("AiFeedback") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("FreeTextResponse") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("IsCorrect") - .HasColumnType("bit"); - - b.Property("QuestionId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("TestId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("QuestionId"); - - b.HasIndex("TestId"); - - b.ToTable("TestAnswers"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CompletedAt") - .HasColumnType("datetime2"); - - b.Property("DifficultyLevel") - .HasColumnType("int"); - - b.Property("Passed") - .HasColumnType("bit"); - - b.Property("Score") - .HasColumnType("int"); - - b.Property("UserId") - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("Tests"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TrueFalseQuestions", b => - { - b.Property("QuestionId") - .HasColumnType("nvarchar(450)"); - - b.Property("CorrectAnswer") - .HasColumnType("bit"); - - b.Property("Explanation") - .HasColumnType("nvarchar(max)"); - - b.HasKey("QuestionId"); - - b.ToTable("TrueFalseQuestions"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.UserExperiences", b => - { - b.Property("Id") - .HasColumnType("nvarchar(450)"); - - b.Property("CompanyName") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("EndDate") - .HasColumnType("datetime2"); - - b.Property("JobTitle") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("StartDate") - .HasColumnType("datetime2"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - - b.Property("isVerified") - .HasColumnType("bit"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("UserExperiences"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Users", b => - { - b.HasBaseType("Microsoft.AspNetCore.Identity.IdentityUser"); - - b.Property("Bio") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("CompanyId") - .HasColumnType("nvarchar(450)"); - - b.Property("CompanyRole") - .HasColumnType("nvarchar(max)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("FirstName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("Headline") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("LastName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("ProfilePicture") - .IsRequired() - .HasColumnType("varbinary(max)"); - - b.HasIndex("CompanyId"); - - b.HasDiscriminator().HasValue("Users"); - }); - - modelBuilder.Entity("JobQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Job", null) - .WithMany() - .HasForeignKey("JobsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("SkillProof.Entities.Models.Questions", null) - .WithMany() - .HasForeignKey("QuestionsId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.CodeCompletionQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("CodeCompletionQuestion") - .HasForeignKey("SkillProof.Entities.Models.CodeCompletionQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.FillInTheBlankQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("FillInTheBlankQuestions") - .HasForeignKey("SkillProof.Entities.Models.FillInTheBlankQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Job", b => - { - b.HasOne("SkillProof.Entities.Models.Companies", "Company") - .WithMany("Jobs") - .HasForeignKey("CompanyId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Company"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.JobApplication", b => - { - b.HasOne("SkillProof.Entities.Models.Job", "Job") - .WithMany("JobApplications") - .HasForeignKey("JobId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("SkillProof.Entities.Models.Tests", "Test") - .WithOne("JobApplication") - .HasForeignKey("SkillProof.Entities.Models.JobApplication", "TestId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("SkillProof.Entities.Models.Users", "User") - .WithMany("JobApplications") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Job"); - - b.Navigation("Test"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.MultipleChoiceQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("MultipleChoiceQuestion") - .HasForeignKey("SkillProof.Entities.Models.MultipleChoiceQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TestAnswers", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithMany("TestAnswers") - .HasForeignKey("QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("SkillProof.Entities.Models.Tests", "Test") - .WithMany("TestAnswers") - .HasForeignKey("TestId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - - b.Navigation("Test"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => - { - b.HasOne("SkillProof.Entities.Models.Users", "User") - .WithMany("Tests") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); - - b.Navigation("User"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.TrueFalseQuestions", b => - { - b.HasOne("SkillProof.Entities.Models.Questions", "Question") - .WithOne("TrueFalseQuestion") - .HasForeignKey("SkillProof.Entities.Models.TrueFalseQuestions", "QuestionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Question"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.UserExperiences", b => - { - b.HasOne("SkillProof.Entities.Models.Users", "User") - .WithMany("UserExperiences") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Users", b => - { - b.HasOne("SkillProof.Entities.Models.Companies", "Companies") - .WithMany("Users") - .HasForeignKey("CompanyId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Companies"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Companies", b => - { - b.Navigation("Jobs"); - - b.Navigation("Users"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Job", b => - { - b.Navigation("JobApplications"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Questions", b => - { - b.Navigation("CodeCompletionQuestion"); - - b.Navigation("FillInTheBlankQuestions"); - - b.Navigation("MultipleChoiceQuestion"); - - b.Navigation("TestAnswers"); - - b.Navigation("TrueFalseQuestion"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => - { - b.Navigation("JobApplication"); - - b.Navigation("TestAnswers"); - }); - - modelBuilder.Entity("SkillProof.Entities.Models.Users", b => - { - b.Navigation("JobApplications"); - - b.Navigation("Tests"); - - b.Navigation("UserExperiences"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/SkillProof/SkillProof.Data/Migrations/20260421091623_AddJobQuestions.cs b/backend/SkillProof/SkillProof.Data/Migrations/20260421091623_AddJobQuestions.cs deleted file mode 100644 index 5ace95e..0000000 --- a/backend/SkillProof/SkillProof.Data/Migrations/20260421091623_AddJobQuestions.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace SkillProof.Data.Migrations -{ - /// - public partial class AddJobQuestions : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "JobQuestions", - columns: table => new - { - JobsId = table.Column(type: "nvarchar(450)", nullable: false), - QuestionsId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_JobQuestions", x => new { x.JobsId, x.QuestionsId }); - table.ForeignKey( - name: "FK_JobQuestions_Jobs_JobsId", - column: x => x.JobsId, - principalTable: "Jobs", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_JobQuestions_Questions_QuestionsId", - column: x => x.QuestionsId, - principalTable: "Questions", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_JobQuestions_QuestionsId", - table: "JobQuestions", - column: "QuestionsId"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "JobQuestions"); - } - } -} diff --git a/backend/SkillProof/SkillProof.Data/Migrations/20260421101825_AddAssessmentStructure.cs b/backend/SkillProof/SkillProof.Data/Migrations/20260421101825_AddAssessmentStructure.cs deleted file mode 100644 index 19e7e83..0000000 --- a/backend/SkillProof/SkillProof.Data/Migrations/20260421101825_AddAssessmentStructure.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace SkillProof.Data.Migrations -{ - /// - public partial class AddAssessmentStructure : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Assessments", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - Title = table.Column(type: "nvarchar(255)", maxLength: 255, nullable: false), - Description = table.Column(type: "nvarchar(max)", nullable: false), - DifficultyLevel = table.Column(type: "int", nullable: false), - CreatedBy = table.Column(type: "nvarchar(max)", nullable: false), - CreatedAt = table.Column(type: "datetime2", nullable: false), - IsActive = table.Column(type: "bit", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Assessments", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AssessmentsJob", - columns: table => new - { - AssessmentsId = table.Column(type: "nvarchar(450)", nullable: false), - JobsId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AssessmentsJob", x => new { x.AssessmentsId, x.JobsId }); - table.ForeignKey( - name: "FK_AssessmentsJob_Assessments_AssessmentsId", - column: x => x.AssessmentsId, - principalTable: "Assessments", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AssessmentsJob_Jobs_JobsId", - column: x => x.JobsId, - principalTable: "Jobs", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AssessmentsQuestions", - columns: table => new - { - AssessmentsId = table.Column(type: "nvarchar(450)", nullable: false), - QuestionsId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AssessmentsQuestions", x => new { x.AssessmentsId, x.QuestionsId }); - table.ForeignKey( - name: "FK_AssessmentsQuestions_Assessments_AssessmentsId", - column: x => x.AssessmentsId, - principalTable: "Assessments", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AssessmentsQuestions_Questions_QuestionsId", - column: x => x.QuestionsId, - principalTable: "Questions", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AssessmentsTests", - columns: table => new - { - AssessmentsId = table.Column(type: "nvarchar(450)", nullable: false), - TestAttemptsId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AssessmentsTests", x => new { x.AssessmentsId, x.TestAttemptsId }); - table.ForeignKey( - name: "FK_AssessmentsTests_Assessments_AssessmentsId", - column: x => x.AssessmentsId, - principalTable: "Assessments", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AssessmentsTests_Tests_TestAttemptsId", - column: x => x.TestAttemptsId, - principalTable: "Tests", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_AssessmentsJob_JobsId", - table: "AssessmentsJob", - column: "JobsId"); - - migrationBuilder.CreateIndex( - name: "IX_AssessmentsQuestions_QuestionsId", - table: "AssessmentsQuestions", - column: "QuestionsId"); - - migrationBuilder.CreateIndex( - name: "IX_AssessmentsTests_TestAttemptsId", - table: "AssessmentsTests", - column: "TestAttemptsId"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AssessmentsJob"); - - migrationBuilder.DropTable( - name: "AssessmentsQuestions"); - - migrationBuilder.DropTable( - name: "AssessmentsTests"); - - migrationBuilder.DropTable( - name: "Assessments"); - } - } -} diff --git a/backend/SkillProof/SkillProof.Data/Migrations/20260421101825_AddAssessmentStructure.Designer.cs b/backend/SkillProof/SkillProof.Data/Migrations/20260520213503_Init.Designer.cs similarity index 85% rename from backend/SkillProof/SkillProof.Data/Migrations/20260421101825_AddAssessmentStructure.Designer.cs rename to backend/SkillProof/SkillProof.Data/Migrations/20260520213503_Init.Designer.cs index d5a2c39..ed2d662 100644 --- a/backend/SkillProof/SkillProof.Data/Migrations/20260421101825_AddAssessmentStructure.Designer.cs +++ b/backend/SkillProof/SkillProof.Data/Migrations/20260520213503_Init.Designer.cs @@ -12,8 +12,8 @@ namespace SkillProof.Data.Migrations { [DbContext(typeof(SkillProofDbContext))] - [Migration("20260421101825_AddAssessmentStructure")] - partial class AddAssessmentStructure + [Migration("20260520213503_Init")] + partial class Init { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -85,6 +85,21 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("JobQuestions"); }); + modelBuilder.Entity("JobUsers", b => + { + b.Property("SavedJobsId") + .HasColumnType("nvarchar(450)"); + + b.Property("UsersId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("SavedJobsId", "UsersId"); + + b.HasIndex("UsersId"); + + b.ToTable("UserSavedJobs", (string)null); + }); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => { b.Property("Id") @@ -293,6 +308,21 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("AspNetUserTokens", (string)null); }); + modelBuilder.Entity("SkillModelUsers", b => + { + b.Property("SkillsId") + .HasColumnType("nvarchar(450)"); + + b.Property("UsersId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("SkillsId", "UsersId"); + + b.HasIndex("UsersId"); + + b.ToTable("SkillModelUsers"); + }); + modelBuilder.Entity("SkillProof.Entities.Models.Assessments", b => { b.Property("Id") @@ -315,6 +345,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("IsActive") .HasColumnType("bit"); + b.Property("SkillId") + .HasColumnType("nvarchar(450)"); + b.Property("Title") .IsRequired() .HasMaxLength(255) @@ -322,6 +355,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("SkillId"); + b.ToTable("Assessments"); }); @@ -370,6 +405,44 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Companies"); }); + modelBuilder.Entity("SkillProof.Entities.Models.Education", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("Degree") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("FieldOfStudy") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("School") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Educations"); + }); + modelBuilder.Entity("SkillProof.Entities.Models.FillInTheBlankQuestions", b => { b.Property("QuestionId") @@ -412,6 +485,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasMaxLength(100) .HasColumnType("nvarchar(100)"); + b.Property("Salary") + .HasColumnType("int"); + + b.Property("ShortDescription") + .IsRequired() + .HasColumnType("nvarchar(max)"); + b.Property("Tags") .IsRequired() .HasMaxLength(500) @@ -437,6 +517,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("AppliedAt") .HasColumnType("datetime2"); + b.Property("IsRead") + .HasColumnType("bit"); + b.Property("JobId") .IsRequired() .HasColumnType("nvarchar(450)"); @@ -513,6 +596,10 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("nvarchar(max)"); + b.PrimitiveCollection("Tags") + .IsRequired() + .HasColumnType("nvarchar(max)"); + b.Property("Title") .IsRequired() .HasMaxLength(255) @@ -529,6 +616,21 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Questions"); }); + modelBuilder.Entity("SkillProof.Entities.Models.SkillModel", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("Skills"); + }); + modelBuilder.Entity("SkillProof.Entities.Models.TestAnswers", b => { b.Property("Id") @@ -544,13 +646,22 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasMaxLength(500) .HasColumnType("nvarchar(500)"); + b.Property("Inspected") + .HasColumnType("bit"); + b.Property("IsCorrect") .HasColumnType("bit"); + b.Property("ManualFeedback") + .HasColumnType("nvarchar(max)"); + b.Property("QuestionId") .IsRequired() .HasColumnType("nvarchar(450)"); + b.Property("Score") + .HasColumnType("float"); + b.Property("TestId") .IsRequired() .HasColumnType("nvarchar(450)"); @@ -578,8 +689,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Passed") .HasColumnType("bit"); - b.Property("Score") - .HasColumnType("int"); + b.Property("Score") + .HasColumnType("float"); b.Property("UserId") .HasColumnType("nvarchar(450)"); @@ -744,6 +855,21 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsRequired(); }); + modelBuilder.Entity("JobUsers", b => + { + b.HasOne("SkillProof.Entities.Models.Job", null) + .WithMany() + .HasForeignKey("SavedJobsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SkillProof.Entities.Models.Users", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => { b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) @@ -795,6 +921,30 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsRequired(); }); + modelBuilder.Entity("SkillModelUsers", b => + { + b.HasOne("SkillProof.Entities.Models.SkillModel", null) + .WithMany() + .HasForeignKey("SkillsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SkillProof.Entities.Models.Users", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SkillProof.Entities.Models.Assessments", b => + { + b.HasOne("SkillProof.Entities.Models.SkillModel", "Skill") + .WithMany("Assessments") + .HasForeignKey("SkillId"); + + b.Navigation("Skill"); + }); + modelBuilder.Entity("SkillProof.Entities.Models.CodeCompletionQuestions", b => { b.HasOne("SkillProof.Entities.Models.Questions", "Question") @@ -806,6 +956,17 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("Question"); }); + modelBuilder.Entity("SkillProof.Entities.Models.Education", b => + { + b.HasOne("SkillProof.Entities.Models.Users", "User") + .WithMany("Educations") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + modelBuilder.Entity("SkillProof.Entities.Models.FillInTheBlankQuestions", b => { b.HasOne("SkillProof.Entities.Models.Questions", "Question") @@ -844,7 +1005,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasOne("SkillProof.Entities.Models.Users", "User") .WithMany("JobApplications") .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) + .OnDelete(DeleteBehavior.NoAction) .IsRequired(); b.Navigation("Job"); @@ -951,6 +1112,11 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Navigation("TrueFalseQuestion"); }); + modelBuilder.Entity("SkillProof.Entities.Models.SkillModel", b => + { + b.Navigation("Assessments"); + }); + modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => { b.Navigation("JobApplication"); @@ -960,6 +1126,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) modelBuilder.Entity("SkillProof.Entities.Models.Users", b => { + b.Navigation("Educations"); + b.Navigation("JobApplications"); b.Navigation("Tests"); diff --git a/backend/SkillProof/SkillProof.Data/Migrations/20260319193050_InitialCreate.cs b/backend/SkillProof/SkillProof.Data/Migrations/20260520213503_Init.cs similarity index 65% rename from backend/SkillProof/SkillProof.Data/Migrations/20260319193050_InitialCreate.cs rename to backend/SkillProof/SkillProof.Data/Migrations/20260520213503_Init.cs index 4c944a9..0484dc7 100644 --- a/backend/SkillProof/SkillProof.Data/Migrations/20260319193050_InitialCreate.cs +++ b/backend/SkillProof/SkillProof.Data/Migrations/20260520213503_Init.cs @@ -6,7 +6,7 @@ namespace SkillProof.Data.Migrations { /// - public partial class InitialCreate : Migration + public partial class Init : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) @@ -25,6 +25,75 @@ protected override void Up(MigrationBuilder migrationBuilder) table.PrimaryKey("PK_AspNetRoles", x => x.Id); }); + migrationBuilder.CreateTable( + name: "Companies", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + Description = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: false), + Website = table.Column(type: "nvarchar(max)", nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Companies", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Questions", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Type = table.Column(type: "int", nullable: false), + Language = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + Difficulty = table.Column(type: "int", nullable: false), + Title = table.Column(type: "nvarchar(255)", maxLength: 255, nullable: false), + QuestionText = table.Column(type: "nvarchar(max)", nullable: false), + Tags = table.Column(type: "nvarchar(max)", nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false), + UpdatedAt = table.Column(type: "datetime2", nullable: false), + CreatedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false), + IsActive = table.Column(type: "bit", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Questions", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Skills", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Skills", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "AspNetUsers", columns: table => new @@ -33,10 +102,12 @@ protected override void Up(MigrationBuilder migrationBuilder) Discriminator = table.Column(type: "nvarchar(13)", maxLength: 13, nullable: false), FirstName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), LastName = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: true), - ProfilePictureUrl = table.Column(type: "nvarchar(max)", nullable: true), + ProfilePicture = table.Column(type: "varbinary(max)", nullable: true), Headline = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), Bio = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), CreatedAt = table.Column(type: "datetime2", nullable: true), + CompanyId = table.Column(type: "nvarchar(450)", nullable: true), + CompanyRole = table.Column(type: "nvarchar(max)", nullable: true), UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), Email = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), @@ -55,64 +126,140 @@ protected override void Up(MigrationBuilder migrationBuilder) constraints: table => { table.PrimaryKey("PK_AspNetUsers", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUsers_Companies_CompanyId", + column: x => x.CompanyId, + principalTable: "Companies", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); }); migrationBuilder.CreateTable( - name: "Companies", + name: "Jobs", columns: table => new { Id = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + CompanyId = table.Column(type: "nvarchar(450)", nullable: false), + Title = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), Description = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: false), - Website = table.Column(type: "nvarchar(max)", nullable: false), - CreatedAt = table.Column(type: "datetime2", nullable: false) + ShortDescription = table.Column(type: "nvarchar(max)", nullable: false), + Location = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + Salary = table.Column(type: "int", nullable: false), + Tags = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false), + EmploymentType = table.Column(type: "int", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_Companies", x => x.Id); + table.PrimaryKey("PK_Jobs", x => x.Id); + table.ForeignKey( + name: "FK_Jobs_Companies_CompanyId", + column: x => x.CompanyId, + principalTable: "Companies", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( - name: "Questions", + name: "CodeCompletionQuestions", columns: table => new { - Id = table.Column(type: "nvarchar(450)", nullable: false), - Type = table.Column(type: "int", nullable: false), - Language = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), - Difficulty = table.Column(type: "int", nullable: false), - Title = table.Column(type: "nvarchar(255)", maxLength: 255, nullable: false), - QuestionText = table.Column(type: "nvarchar(max)", nullable: false), - CreatedAt = table.Column(type: "datetime2", nullable: false), - UpdatedAt = table.Column(type: "datetime2", nullable: false), - CreatedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false), - IsActive = table.Column(type: "bit", nullable: false) + QuestionId = table.Column(type: "nvarchar(450)", nullable: false), + CodeSnippet = table.Column(type: "nvarchar(max)", nullable: false), + AcceptedAnswers = table.Column(type: "nvarchar(max)", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_Questions", x => x.Id); + table.PrimaryKey("PK_CodeCompletionQuestions", x => x.QuestionId); + table.ForeignKey( + name: "FK_CodeCompletionQuestions_Questions_QuestionId", + column: x => x.QuestionId, + principalTable: "Questions", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( - name: "AspNetRoleClaims", + name: "FillInTheBlankQuestions", columns: table => new { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - RoleId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + QuestionId = table.Column(type: "nvarchar(450)", nullable: false), + Answer = table.Column(type: "nvarchar(max)", nullable: false), + manualFeedback = table.Column(type: "nvarchar(max)", nullable: true) }, constraints: table => { - table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.PrimaryKey("PK_FillInTheBlankQuestions", x => x.QuestionId); table.ForeignKey( - name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", + name: "FK_FillInTheBlankQuestions_Questions_QuestionId", + column: x => x.QuestionId, + principalTable: "Questions", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "MultipleChoiceQuestion", + columns: table => new + { + QuestionId = table.Column(type: "nvarchar(450)", nullable: false), + Options = table.Column(type: "nvarchar(max)", nullable: false), + CorrectAnswerIds = table.Column(type: "nvarchar(max)", nullable: false), + AllowMultipleSelection = table.Column(type: "bit", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MultipleChoiceQuestion", x => x.QuestionId); + table.ForeignKey( + name: "FK_MultipleChoiceQuestion_Questions_QuestionId", + column: x => x.QuestionId, + principalTable: "Questions", principalColumn: "Id", onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "TrueFalseQuestions", + columns: table => new + { + QuestionId = table.Column(type: "nvarchar(450)", nullable: false), + CorrectAnswer = table.Column(type: "bit", nullable: false), + Explanation = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_TrueFalseQuestions", x => x.QuestionId); + table.ForeignKey( + name: "FK_TrueFalseQuestions_Questions_QuestionId", + column: x => x.QuestionId, + principalTable: "Questions", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Assessments", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Title = table.Column(type: "nvarchar(255)", maxLength: 255, nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: false), + DifficultyLevel = table.Column(type: "int", nullable: false), + CreatedBy = table.Column(type: "nvarchar(max)", nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false), + IsActive = table.Column(type: "bit", nullable: false), + SkillId = table.Column(type: "nvarchar(450)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Assessments", x => x.Id); + table.ForeignKey( + name: "FK_Assessments_Skills_SkillId", + column: x => x.SkillId, + principalTable: "Skills", + principalColumn: "Id"); + }); + migrationBuilder.CreateTable( name: "AspNetUserClaims", columns: table => new @@ -198,6 +345,54 @@ protected override void Up(MigrationBuilder migrationBuilder) onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "Educations", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + School = table.Column(type: "nvarchar(max)", nullable: false), + Degree = table.Column(type: "nvarchar(max)", nullable: false), + FieldOfStudy = table.Column(type: "nvarchar(max)", nullable: false), + StartDate = table.Column(type: "datetime2", nullable: false), + EndDate = table.Column(type: "datetime2", nullable: true), + Description = table.Column(type: "nvarchar(max)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Educations", x => x.Id); + table.ForeignKey( + name: "FK_Educations_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "SkillModelUsers", + columns: table => new + { + SkillsId = table.Column(type: "nvarchar(450)", nullable: false), + UsersId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SkillModelUsers", x => new { x.SkillsId, x.UsersId }); + table.ForeignKey( + name: "FK_SkillModelUsers_AspNetUsers_UsersId", + column: x => x.UsersId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_SkillModelUsers_Skills_SkillsId", + column: x => x.SkillsId, + principalTable: "Skills", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "Tests", columns: table => new @@ -206,7 +401,7 @@ protected override void Up(MigrationBuilder migrationBuilder) DifficultyLevel = table.Column(type: "int", nullable: false), UserId = table.Column(type: "nvarchar(450)", nullable: true), Passed = table.Column(type: "bit", nullable: false), - Score = table.Column(type: "int", nullable: false), + Score = table.Column(type: "float", nullable: false), CompletedAt = table.Column(type: "datetime2", nullable: false) }, constraints: table => @@ -244,134 +439,120 @@ protected override void Up(MigrationBuilder migrationBuilder) }); migrationBuilder.CreateTable( - name: "CompaniesUsers", + name: "JobQuestions", columns: table => new { - CompaniesId = table.Column(type: "nvarchar(450)", nullable: false), - UsersId = table.Column(type: "nvarchar(450)", nullable: false) + JobsId = table.Column(type: "nvarchar(450)", nullable: false), + QuestionsId = table.Column(type: "nvarchar(450)", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_CompaniesUsers", x => new { x.CompaniesId, x.UsersId }); + table.PrimaryKey("PK_JobQuestions", x => new { x.JobsId, x.QuestionsId }); table.ForeignKey( - name: "FK_CompaniesUsers_AspNetUsers_UsersId", - column: x => x.UsersId, - principalTable: "AspNetUsers", + name: "FK_JobQuestions_Jobs_JobsId", + column: x => x.JobsId, + principalTable: "Jobs", principalColumn: "Id", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_CompaniesUsers_Companies_CompaniesId", - column: x => x.CompaniesId, - principalTable: "Companies", + name: "FK_JobQuestions_Questions_QuestionsId", + column: x => x.QuestionsId, + principalTable: "Questions", principalColumn: "Id", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( - name: "Jobs", + name: "UserSavedJobs", columns: table => new { - Id = table.Column(type: "nvarchar(450)", nullable: false), - CompanyId = table.Column(type: "nvarchar(450)", nullable: false), - Title = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - Description = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: false), - Location = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - Tags = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), - CreatedAt = table.Column(type: "datetime2", nullable: false), - EmploymentType = table.Column(type: "int", nullable: false) + SavedJobsId = table.Column(type: "nvarchar(450)", nullable: false), + UsersId = table.Column(type: "nvarchar(450)", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_Jobs", x => x.Id); + table.PrimaryKey("PK_UserSavedJobs", x => new { x.SavedJobsId, x.UsersId }); table.ForeignKey( - name: "FK_Jobs_Companies_CompanyId", - column: x => x.CompanyId, - principalTable: "Companies", + name: "FK_UserSavedJobs_AspNetUsers_UsersId", + column: x => x.UsersId, + principalTable: "AspNetUsers", principalColumn: "Id", onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "CodeCompletionQuestions", - columns: table => new - { - QuestionId = table.Column(type: "nvarchar(450)", nullable: false), - CodeSnippet = table.Column(type: "nvarchar(max)", nullable: false), - AcceptedAnswers = table.Column(type: "nvarchar(max)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CodeCompletionQuestions", x => x.QuestionId); table.ForeignKey( - name: "FK_CodeCompletionQuestions_Questions_QuestionId", - column: x => x.QuestionId, - principalTable: "Questions", + name: "FK_UserSavedJobs_Jobs_SavedJobsId", + column: x => x.SavedJobsId, + principalTable: "Jobs", principalColumn: "Id", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( - name: "FillInTheBlankQuestions", + name: "AssessmentsJob", columns: table => new { - QuestionId = table.Column(type: "nvarchar(450)", nullable: false), - Answer = table.Column(type: "nvarchar(max)", nullable: false), - manualFeedback = table.Column(type: "nvarchar(max)", nullable: true) + AssessmentsId = table.Column(type: "nvarchar(450)", nullable: false), + JobsId = table.Column(type: "nvarchar(450)", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_FillInTheBlankQuestions", x => x.QuestionId); + table.PrimaryKey("PK_AssessmentsJob", x => new { x.AssessmentsId, x.JobsId }); table.ForeignKey( - name: "FK_FillInTheBlankQuestions_Questions_QuestionId", - column: x => x.QuestionId, - principalTable: "Questions", + name: "FK_AssessmentsJob_Assessments_AssessmentsId", + column: x => x.AssessmentsId, + principalTable: "Assessments", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AssessmentsJob_Jobs_JobsId", + column: x => x.JobsId, + principalTable: "Jobs", principalColumn: "Id", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( - name: "MultipleChoiceQuestion", + name: "AssessmentsQuestions", columns: table => new { - QuestionId = table.Column(type: "nvarchar(450)", nullable: false), - Options = table.Column(type: "nvarchar(max)", nullable: false), - CorrectAnswerIds = table.Column(type: "nvarchar(max)", nullable: false), - AllowMultipleSelection = table.Column(type: "bit", nullable: false) + AssessmentsId = table.Column(type: "nvarchar(450)", nullable: false), + QuestionsId = table.Column(type: "nvarchar(450)", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_MultipleChoiceQuestion", x => x.QuestionId); + table.PrimaryKey("PK_AssessmentsQuestions", x => new { x.AssessmentsId, x.QuestionsId }); table.ForeignKey( - name: "FK_MultipleChoiceQuestion_Questions_QuestionId", - column: x => x.QuestionId, + name: "FK_AssessmentsQuestions_Assessments_AssessmentsId", + column: x => x.AssessmentsId, + principalTable: "Assessments", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AssessmentsQuestions_Questions_QuestionsId", + column: x => x.QuestionsId, principalTable: "Questions", principalColumn: "Id", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( - name: "TestAnswers", + name: "AssessmentsTests", columns: table => new { - Id = table.Column(type: "nvarchar(450)", nullable: false), - QuestionId = table.Column(type: "nvarchar(450)", nullable: false), - TestId = table.Column(type: "nvarchar(450)", nullable: false), - FreeTextResponse = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), - IsCorrect = table.Column(type: "bit", nullable: false), - AiFeedback = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false) + AssessmentsId = table.Column(type: "nvarchar(450)", nullable: false), + TestAttemptsId = table.Column(type: "nvarchar(450)", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_TestAnswers", x => x.Id); + table.PrimaryKey("PK_AssessmentsTests", x => new { x.AssessmentsId, x.TestAttemptsId }); table.ForeignKey( - name: "FK_TestAnswers_Questions_QuestionId", - column: x => x.QuestionId, - principalTable: "Questions", + name: "FK_AssessmentsTests_Assessments_AssessmentsId", + column: x => x.AssessmentsId, + principalTable: "Assessments", principalColumn: "Id", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_TestAnswers_Tests_TestId", - column: x => x.TestId, + name: "FK_AssessmentsTests_Tests_TestAttemptsId", + column: x => x.TestAttemptsId, principalTable: "Tests", principalColumn: "Id", onDelete: ReferentialAction.Cascade); @@ -386,6 +567,7 @@ protected override void Up(MigrationBuilder migrationBuilder) UserId = table.Column(type: "nvarchar(450)", nullable: false), TestId = table.Column(type: "nvarchar(450)", nullable: true), Status = table.Column(type: "int", nullable: false), + IsRead = table.Column(type: "bit", nullable: false), AppliedAt = table.Column(type: "datetime2", nullable: false) }, constraints: table => @@ -395,8 +577,7 @@ protected override void Up(MigrationBuilder migrationBuilder) name: "FK_JobApplications_AspNetUsers_UserId", column: x => x.UserId, principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); + principalColumn: "Id"); table.ForeignKey( name: "FK_JobApplications_Jobs_JobId", column: x => x.JobId, @@ -411,6 +592,37 @@ protected override void Up(MigrationBuilder migrationBuilder) onDelete: ReferentialAction.Restrict); }); + migrationBuilder.CreateTable( + name: "TestAnswers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + QuestionId = table.Column(type: "nvarchar(450)", nullable: false), + TestId = table.Column(type: "nvarchar(450)", nullable: false), + FreeTextResponse = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + IsCorrect = table.Column(type: "bit", nullable: false), + Score = table.Column(type: "float", nullable: false), + AiFeedback = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + ManualFeedback = table.Column(type: "nvarchar(max)", nullable: true), + Inspected = table.Column(type: "bit", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TestAnswers", x => x.Id); + table.ForeignKey( + name: "FK_TestAnswers_Questions_QuestionId", + column: x => x.QuestionId, + principalTable: "Questions", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_TestAnswers_Tests_TestId", + column: x => x.TestId, + principalTable: "Tests", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateIndex( name: "IX_AspNetRoleClaims_RoleId", table: "AspNetRoleClaims", @@ -443,6 +655,11 @@ protected override void Up(MigrationBuilder migrationBuilder) table: "AspNetUsers", column: "NormalizedEmail"); + migrationBuilder.CreateIndex( + name: "IX_AspNetUsers_CompanyId", + table: "AspNetUsers", + column: "CompanyId"); + migrationBuilder.CreateIndex( name: "UserNameIndex", table: "AspNetUsers", @@ -451,9 +668,29 @@ protected override void Up(MigrationBuilder migrationBuilder) filter: "[NormalizedUserName] IS NOT NULL"); migrationBuilder.CreateIndex( - name: "IX_CompaniesUsers_UsersId", - table: "CompaniesUsers", - column: "UsersId"); + name: "IX_Assessments_SkillId", + table: "Assessments", + column: "SkillId"); + + migrationBuilder.CreateIndex( + name: "IX_AssessmentsJob_JobsId", + table: "AssessmentsJob", + column: "JobsId"); + + migrationBuilder.CreateIndex( + name: "IX_AssessmentsQuestions_QuestionsId", + table: "AssessmentsQuestions", + column: "QuestionsId"); + + migrationBuilder.CreateIndex( + name: "IX_AssessmentsTests_TestAttemptsId", + table: "AssessmentsTests", + column: "TestAttemptsId"); + + migrationBuilder.CreateIndex( + name: "IX_Educations_UserId", + table: "Educations", + column: "UserId"); migrationBuilder.CreateIndex( name: "IX_JobApplications_JobId", @@ -472,11 +709,21 @@ protected override void Up(MigrationBuilder migrationBuilder) table: "JobApplications", column: "UserId"); + migrationBuilder.CreateIndex( + name: "IX_JobQuestions_QuestionsId", + table: "JobQuestions", + column: "QuestionsId"); + migrationBuilder.CreateIndex( name: "IX_Jobs_CompanyId", table: "Jobs", column: "CompanyId"); + migrationBuilder.CreateIndex( + name: "IX_SkillModelUsers_UsersId", + table: "SkillModelUsers", + column: "UsersId"); + migrationBuilder.CreateIndex( name: "IX_TestAnswers_QuestionId", table: "TestAnswers", @@ -496,6 +743,11 @@ protected override void Up(MigrationBuilder migrationBuilder) name: "IX_UserExperiences_UserId", table: "UserExperiences", column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_UserSavedJobs_UsersId", + table: "UserSavedJobs", + column: "UsersId"); } /// @@ -516,11 +768,20 @@ protected override void Down(MigrationBuilder migrationBuilder) migrationBuilder.DropTable( name: "AspNetUserTokens"); + migrationBuilder.DropTable( + name: "AssessmentsJob"); + + migrationBuilder.DropTable( + name: "AssessmentsQuestions"); + + migrationBuilder.DropTable( + name: "AssessmentsTests"); + migrationBuilder.DropTable( name: "CodeCompletionQuestions"); migrationBuilder.DropTable( - name: "CompaniesUsers"); + name: "Educations"); migrationBuilder.DropTable( name: "FillInTheBlankQuestions"); @@ -528,32 +789,50 @@ protected override void Down(MigrationBuilder migrationBuilder) migrationBuilder.DropTable( name: "JobApplications"); + migrationBuilder.DropTable( + name: "JobQuestions"); + migrationBuilder.DropTable( name: "MultipleChoiceQuestion"); + migrationBuilder.DropTable( + name: "SkillModelUsers"); + migrationBuilder.DropTable( name: "TestAnswers"); + migrationBuilder.DropTable( + name: "TrueFalseQuestions"); + migrationBuilder.DropTable( name: "UserExperiences"); + migrationBuilder.DropTable( + name: "UserSavedJobs"); + migrationBuilder.DropTable( name: "AspNetRoles"); migrationBuilder.DropTable( - name: "Jobs"); + name: "Assessments"); + + migrationBuilder.DropTable( + name: "Tests"); migrationBuilder.DropTable( name: "Questions"); migrationBuilder.DropTable( - name: "Tests"); + name: "Jobs"); migrationBuilder.DropTable( - name: "Companies"); + name: "Skills"); migrationBuilder.DropTable( name: "AspNetUsers"); + + migrationBuilder.DropTable( + name: "Companies"); } } } diff --git a/backend/SkillProof/SkillProof.Data/Migrations/SkillProofDbContextModelSnapshot.cs b/backend/SkillProof/SkillProof.Data/Migrations/SkillProofDbContextModelSnapshot.cs index afd4d27..009de02 100644 --- a/backend/SkillProof/SkillProof.Data/Migrations/SkillProofDbContextModelSnapshot.cs +++ b/backend/SkillProof/SkillProof.Data/Migrations/SkillProofDbContextModelSnapshot.cs @@ -82,6 +82,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("JobQuestions"); }); + modelBuilder.Entity("JobUsers", b => + { + b.Property("SavedJobsId") + .HasColumnType("nvarchar(450)"); + + b.Property("UsersId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("SavedJobsId", "UsersId"); + + b.HasIndex("UsersId"); + + b.ToTable("UserSavedJobs", (string)null); + }); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => { b.Property("Id") @@ -290,6 +305,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("AspNetUserTokens", (string)null); }); + modelBuilder.Entity("SkillModelUsers", b => + { + b.Property("SkillsId") + .HasColumnType("nvarchar(450)"); + + b.Property("UsersId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("SkillsId", "UsersId"); + + b.HasIndex("UsersId"); + + b.ToTable("SkillModelUsers"); + }); + modelBuilder.Entity("SkillProof.Entities.Models.Assessments", b => { b.Property("Id") @@ -312,6 +342,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("IsActive") .HasColumnType("bit"); + b.Property("SkillId") + .HasColumnType("nvarchar(450)"); + b.Property("Title") .IsRequired() .HasMaxLength(255) @@ -319,6 +352,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("SkillId"); + b.ToTable("Assessments"); }); @@ -367,6 +402,44 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Companies"); }); + modelBuilder.Entity("SkillProof.Entities.Models.Education", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("Degree") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("FieldOfStudy") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("School") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Educations"); + }); + modelBuilder.Entity("SkillProof.Entities.Models.FillInTheBlankQuestions", b => { b.Property("QuestionId") @@ -409,6 +482,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(100) .HasColumnType("nvarchar(100)"); + b.Property("Salary") + .HasColumnType("int"); + + b.Property("ShortDescription") + .IsRequired() + .HasColumnType("nvarchar(max)"); + b.Property("Tags") .IsRequired() .HasMaxLength(500) @@ -434,6 +514,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("AppliedAt") .HasColumnType("datetime2"); + b.Property("IsRead") + .HasColumnType("bit"); + b.Property("JobId") .IsRequired() .HasColumnType("nvarchar(450)"); @@ -530,6 +613,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Questions"); }); + modelBuilder.Entity("SkillProof.Entities.Models.SkillModel", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("Skills"); + }); + modelBuilder.Entity("SkillProof.Entities.Models.TestAnswers", b => { b.Property("Id") @@ -545,13 +643,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(500) .HasColumnType("nvarchar(500)"); + b.Property("Inspected") + .HasColumnType("bit"); + b.Property("IsCorrect") .HasColumnType("bit"); + b.Property("ManualFeedback") + .HasColumnType("nvarchar(max)"); + b.Property("QuestionId") .IsRequired() .HasColumnType("nvarchar(450)"); + b.Property("Score") + .HasColumnType("float"); + b.Property("TestId") .IsRequired() .HasColumnType("nvarchar(450)"); @@ -579,8 +686,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Passed") .HasColumnType("bit"); - b.Property("Score") - .HasColumnType("int"); + b.Property("Score") + .HasColumnType("float"); b.Property("UserId") .HasColumnType("nvarchar(450)"); @@ -745,6 +852,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired(); }); + modelBuilder.Entity("JobUsers", b => + { + b.HasOne("SkillProof.Entities.Models.Job", null) + .WithMany() + .HasForeignKey("SavedJobsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SkillProof.Entities.Models.Users", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => { b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) @@ -796,6 +918,30 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired(); }); + modelBuilder.Entity("SkillModelUsers", b => + { + b.HasOne("SkillProof.Entities.Models.SkillModel", null) + .WithMany() + .HasForeignKey("SkillsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("SkillProof.Entities.Models.Users", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SkillProof.Entities.Models.Assessments", b => + { + b.HasOne("SkillProof.Entities.Models.SkillModel", "Skill") + .WithMany("Assessments") + .HasForeignKey("SkillId"); + + b.Navigation("Skill"); + }); + modelBuilder.Entity("SkillProof.Entities.Models.CodeCompletionQuestions", b => { b.HasOne("SkillProof.Entities.Models.Questions", "Question") @@ -807,6 +953,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Question"); }); + modelBuilder.Entity("SkillProof.Entities.Models.Education", b => + { + b.HasOne("SkillProof.Entities.Models.Users", "User") + .WithMany("Educations") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + modelBuilder.Entity("SkillProof.Entities.Models.FillInTheBlankQuestions", b => { b.HasOne("SkillProof.Entities.Models.Questions", "Question") @@ -845,7 +1002,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasOne("SkillProof.Entities.Models.Users", "User") .WithMany("JobApplications") .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) + .OnDelete(DeleteBehavior.NoAction) .IsRequired(); b.Navigation("Job"); @@ -952,6 +1109,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("TrueFalseQuestion"); }); + modelBuilder.Entity("SkillProof.Entities.Models.SkillModel", b => + { + b.Navigation("Assessments"); + }); + modelBuilder.Entity("SkillProof.Entities.Models.Tests", b => { b.Navigation("JobApplication"); @@ -961,6 +1123,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("SkillProof.Entities.Models.Users", b => { + b.Navigation("Educations"); + b.Navigation("JobApplications"); b.Navigation("Tests"); diff --git a/backend/SkillProof/SkillProof.Data/SkillProof.Data.csproj b/backend/SkillProof/SkillProof.Data/SkillProof.Data.csproj index b1c5b57..dbcae77 100644 --- a/backend/SkillProof/SkillProof.Data/SkillProof.Data.csproj +++ b/backend/SkillProof/SkillProof.Data/SkillProof.Data.csproj @@ -21,4 +21,8 @@ + + + + diff --git a/backend/SkillProof/SkillProof.Data/SkillProofDbContext.cs b/backend/SkillProof/SkillProof.Data/SkillProofDbContext.cs index 9480fc2..0681fe5 100644 --- a/backend/SkillProof/SkillProof.Data/SkillProofDbContext.cs +++ b/backend/SkillProof/SkillProof.Data/SkillProofDbContext.cs @@ -20,6 +20,9 @@ public class SkillProofDbContext : IdentityDbContext public DbSet CodeCompletionQuestions { get; set; } public DbSet FillInTheBlankQuestions { get; set; } public DbSet TrueFalseQuestions { get; set; } + public DbSet Educations { get; set; } + + public DbSet Skills { get; set; } public SkillProofDbContext(DbContextOptions options) : base(options) { } @@ -39,6 +42,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.ApplyConfiguration(new TrueFalseQuestionsConfigurations()); modelBuilder.ApplyConfiguration(new UserExperiencesConfigurations()); modelBuilder.ApplyConfiguration(new UsersConfigurations()); + modelBuilder.ApplyConfiguration(new SkillsConfiguration()); } } } diff --git a/backend/SkillProof/SkillProof.Entities/Configurations/EducationConfiguration.cs b/backend/SkillProof/SkillProof.Entities/Configurations/EducationConfiguration.cs new file mode 100644 index 0000000..209a9e8 --- /dev/null +++ b/backend/SkillProof/SkillProof.Entities/Configurations/EducationConfiguration.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SkillProof.Entities.Models; + +namespace SkillProof.Entities.Configurations +{ + public class EducationConfiguration : IEntityTypeConfiguration + { + public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder builder) + { + builder.HasKey(e => e.Id); + + builder.HasOne(e => e.User) + .WithMany(u => u.Educations) + .HasForeignKey(e => e.UserId) + .OnDelete(DeleteBehavior.Cascade); + } + } +} diff --git a/backend/SkillProof/SkillProof.Entities/Configurations/JobbApplicationsConfigurations.cs b/backend/SkillProof/SkillProof.Entities/Configurations/JobbApplicationsConfigurations.cs index b63029d..b5e1c50 100644 --- a/backend/SkillProof/SkillProof.Entities/Configurations/JobbApplicationsConfigurations.cs +++ b/backend/SkillProof/SkillProof.Entities/Configurations/JobbApplicationsConfigurations.cs @@ -14,14 +14,17 @@ public class JobbApplicationsConfigurations : IEntityTypeConfiguration builder) { builder.HasKey(ja => ja.Id); + builder.HasOne(ja => ja.Job) .WithMany(j => j.JobApplications) .HasForeignKey(ja => ja.JobId) .OnDelete(DeleteBehavior.Cascade); + builder.HasOne(ja => ja.User) - .WithMany(ja => ja.JobApplications) + .WithMany(u => u.JobApplications) .HasForeignKey(ja => ja.UserId) - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.NoAction); + builder.HasOne(ja => ja.Test) .WithOne(t => t.JobApplication) .HasForeignKey(ja => ja.TestId) diff --git a/backend/SkillProof/SkillProof.Entities/Configurations/SkillsConfiguration.cs b/backend/SkillProof/SkillProof.Entities/Configurations/SkillsConfiguration.cs new file mode 100644 index 0000000..15e93ee --- /dev/null +++ b/backend/SkillProof/SkillProof.Entities/Configurations/SkillsConfiguration.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using SkillProof.Entities.Models; + +namespace SkillProof.Entities.Configurations +{ + public class SkillsConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(s => s.Id); + + builder.Property(s => s.Name) + .IsRequired() + .HasMaxLength(100); + + // Skill -> Assessments + builder.HasMany(s => s.Assessments) + .WithOne(a => a.Skill) + .HasForeignKey(a => a.SkillId); + + // User <-> Skill many-to-many + builder.HasMany(s => s.Users) + .WithMany(u => u.Skills); + } + } +} diff --git a/backend/SkillProof/SkillProof.Entities/Configurations/UsersConfigurations.cs b/backend/SkillProof/SkillProof.Entities/Configurations/UsersConfigurations.cs index b1e2a70..df49179 100644 --- a/backend/SkillProof/SkillProof.Entities/Configurations/UsersConfigurations.cs +++ b/backend/SkillProof/SkillProof.Entities/Configurations/UsersConfigurations.cs @@ -1,11 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using SkillProof.Entities.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace SkillProof.Entities.Configurations { @@ -18,6 +13,9 @@ public void Configure(EntityTypeBuilder builder) builder.Property(u => u.Email).IsRequired().HasMaxLength(50); builder.Property(u => u.Headline).HasMaxLength(100); builder.Property(u => u.Bio).HasMaxLength(500); + builder.HasMany(u => u.SavedJobs) + .WithMany() + .UsingEntity(j => j.ToTable("UserSavedJobs")); } } } diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Assesment/AddAssesmentsToSkillDto.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Assesment/AddAssesmentsToSkillDto.cs new file mode 100644 index 0000000..5583789 --- /dev/null +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Assesment/AddAssesmentsToSkillDto.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SkillProof.Entities.Dtos.Assesment +{ + public class AddAssesmentsToSkillDto + { + public string SkillId { get; set; } + public string[] AssessmentIds { get; set; } + } +} diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Education/EducationCreateDto.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Education/EducationCreateDto.cs new file mode 100644 index 0000000..33d007f --- /dev/null +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Education/EducationCreateDto.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SkillProof.Entities.Dtos.Education +{ + public class EducationCreateDto + { + public string School { get; set; } = string.Empty; + public string Degree { get; set; } = string.Empty; + public string FieldOfStudy { get; set; } = string.Empty; + public DateTime StartDate { get; set; } + public DateTime? EndDate { get; set; } = null; + public string Description { get; set; } = string.Empty; + } +} diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Education/EducationViewDto.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Education/EducationViewDto.cs new file mode 100644 index 0000000..55311f9 --- /dev/null +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Education/EducationViewDto.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SkillProof.Entities.Dtos.Education +{ + public class EducationViewDto + { + public string Id { get; set; } = string.Empty; + public string School { get; set; } = string.Empty; + public string Degree { get; set; } = string.Empty; + public string FieldOfStudy { get; set; } = string.Empty; + public DateTime StartDate { get; set; } + public DateTime? EndDate { get; set; } + public bool IsOngoing => EndDate == null; + public string Description { get; set; } = string.Empty; + } +} diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Experience/ExperienceCreateDto.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Experience/ExperienceCreateDto.cs new file mode 100644 index 0000000..01c2ba6 --- /dev/null +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Experience/ExperienceCreateDto.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SkillProof.Entities.Dtos.Experience +{ + public class ExperienceCreateDto + { + public string JobTitle { get; set; } = string.Empty; + + public string CompanyName { get; set; } = string.Empty; + + public DateTime StartDate { get; set; } + + public DateTime EndDate { get; set; } + } +} diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Experience/ExperienceViewDto.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Experience/ExperienceViewDto.cs new file mode 100644 index 0000000..9a2de96 --- /dev/null +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Experience/ExperienceViewDto.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SkillProof.Entities.Dtos.Experience +{ + public class ExperienceViewDto + { + public string Id { get; set; } = string.Empty; + public string JobTitle { get; set; } = string.Empty; + public string CompanyName { get; set; } = string.Empty; + public DateTime StartDate { get; set; } + public DateTime EndDate { get; set; } + } +} diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Gemini/GradeAnswerDto.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Gemini/GradeAnswerDto.cs new file mode 100644 index 0000000..49c3902 --- /dev/null +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Gemini/GradeAnswerDto.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SkillProof.Entities.Dtos.Gemini +{ + public class GradeAnswerRequest + { + public string Question { get; set; } = string.Empty; + public string UserAnswer { get; set; } = string.Empty; + } +} diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Jobs/JobCreateDto.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Jobs/JobCreateDto.cs index a7c6635..bcb9f23 100644 --- a/backend/SkillProof/SkillProof.Entities/Dtos/Jobs/JobCreateDto.cs +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Jobs/JobCreateDto.cs @@ -11,6 +11,7 @@ public class JobCreateDto public EmploymentType EmploymentType { get; set; } public string? Salary { get; set; } public string Description { get; set; } + public string ShortDescription { get; set; } public string Tags { get; set; } public List? AssessmentIds { get; set; } } \ No newline at end of file diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Jobs/JobNotificationDto.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Jobs/JobNotificationDto.cs new file mode 100644 index 0000000..958e2a9 --- /dev/null +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Jobs/JobNotificationDto.cs @@ -0,0 +1,17 @@ +using SkillProof.Entities.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SkillProof.Entities.Dtos.Jobs +{ + public class JobNotificationDto + { + public string Id { get; set; } + public string JobTitle { get; set; } + public JobApplicationStatus Status { get; set; } + + } +} diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Jobs/JobViewDto.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Jobs/JobViewDto.cs index f55875c..f6ef4a6 100644 --- a/backend/SkillProof/SkillProof.Entities/Dtos/Jobs/JobViewDto.cs +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Jobs/JobViewDto.cs @@ -17,8 +17,11 @@ public class JobViewDto public string Title { get; set; } public string Description { get; set; } + + public string ShortDescription { get; set; } public string Location { get; set; } + public int Salary { get; set; } public string Tags { get; set; } // Json array of string diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Jobs/UpdateJobDto.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Jobs/UpdateJobDto.cs new file mode 100644 index 0000000..6e70d09 --- /dev/null +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Jobs/UpdateJobDto.cs @@ -0,0 +1,35 @@ +using SkillProof.Entities.Dtos.Assesment; +using SkillProof.Entities.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SkillProof.Entities.Dtos.Jobs +{ + public class UpdateJobDto + { + public string Id { get; set; } + + public string CompanyId { get; set; } + + public EmploymentType EmploymentType { get; set; } + + //public string CompanyName { get; set; } + + public string Title { get; set; } + + public string Description { get; set; } + + public string ShortDescription { get; set; } + + public string Location { get; set; } + public int Salary { get; set; } + + public string Tags { get; set; } // Json array of string + + public List Assessments { get; set; } = new List(); + public List? AssessmentIds { get; set; } + } +} diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Questions/CreateQuestionRequestDto.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Questions/CreateQuestionRequestDto.cs index c202c12..30ff9ab 100644 --- a/backend/SkillProof/SkillProof.Entities/Dtos/Questions/CreateQuestionRequestDto.cs +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Questions/CreateQuestionRequestDto.cs @@ -8,9 +8,8 @@ public class CreateQuestionRequestDto [Required] public QuestionType Type { get; set; } - [Required] [StringLength(20)] - public string Language { get; set; } = string.Empty; + public string? Language { get; set; } [Required] public DifficultyLevel Difficulty { get; set; } diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Questions/QuestionResponseDto.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Questions/QuestionResponseDto.cs index d85a7d9..8b4befc 100644 --- a/backend/SkillProof/SkillProof.Entities/Dtos/Questions/QuestionResponseDto.cs +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Questions/QuestionResponseDto.cs @@ -8,6 +8,7 @@ public class QuestionResponseDto public QuestionType Type { get; set; } public string Language { get; set; } = string.Empty; public DifficultyLevel Difficulty { get; set; } + public List Tags { get; set; } = new(); public string Title { get; set; } = string.Empty; public string QuestionText { get; set; } = string.Empty; public string CreatedBy { get; set; } = string.Empty; diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Questions/UpdateQuestionRequestDto.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Questions/UpdateQuestionRequestDto.cs index e7bb04f..a132407 100644 --- a/backend/SkillProof/SkillProof.Entities/Dtos/Questions/UpdateQuestionRequestDto.cs +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Questions/UpdateQuestionRequestDto.cs @@ -5,13 +5,14 @@ namespace SkillProof.Entities.Dtos.Questions { public class UpdateQuestionRequestDto { - [Required] [StringLength(20)] - public string Language { get; set; } = string.Empty; + public string? Language { get; set; } [Required] public DifficultyLevel Difficulty { get; set; } + public List Tags { get; set; } = new(); + [Required] [StringLength(255)] public string Title { get; set; } = string.Empty; diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Skill/SkillCreateDto.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Skill/SkillCreateDto.cs new file mode 100644 index 0000000..497dbba --- /dev/null +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Skill/SkillCreateDto.cs @@ -0,0 +1,15 @@ +using SkillProof.Entities.Dtos.Assesment; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SkillProof.Entities.Dtos.Skill +{ + public class SkillCreateDto + { + public string Name { get; set; } + public List Assessments { get; set; } = new List(); + } +} diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Skill/ViewSkill.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Skill/ViewSkill.cs new file mode 100644 index 0000000..d21375c --- /dev/null +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Skill/ViewSkill.cs @@ -0,0 +1,16 @@ +using SkillProof.Entities.Dtos.Assesment; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SkillProof.Entities.Dtos.Skill +{ + public class ViewSkill + { + public string Id { get; set; } + public string Name { get; set; } + public List Assessments { get; set; } = new List(); + } +} diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Tests/FeedbackResponseDto.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Tests/FeedbackResponseDto.cs new file mode 100644 index 0000000..cb67abf --- /dev/null +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Tests/FeedbackResponseDto.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SkillProof.Entities.Dtos.Tests +{ + public class FeedbackResponseDto + { + public string TestAnswerId { get; set; } + public double Score { get; set; } + public string QuestionText { get; set; } + public string UserResponse { get; set; } + public bool Inspected { get; set; } + } +} diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Tests/JobApplicationStatusDto.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Tests/JobApplicationStatusDto.cs new file mode 100644 index 0000000..586d5d0 --- /dev/null +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Tests/JobApplicationStatusDto.cs @@ -0,0 +1,15 @@ +using SkillProof.Entities.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SkillProof.Entities.Dtos.Tests +{ + public class JobApplicationStatusDto + { + public JobApplicationStatus jobApplicationStatus { get; set; } + public string UserId { get; set; } + } +} diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Tests/QuestionResultDto.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Tests/QuestionResultDto.cs index c3f2f38..062961c 100644 --- a/backend/SkillProof/SkillProof.Entities/Dtos/Tests/QuestionResultDto.cs +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Tests/QuestionResultDto.cs @@ -8,7 +8,7 @@ public class QuestionResultDto public string QuestionTitle { get; set; } = string.Empty; public QuestionType Type { get; set; } public bool IsCorrect { get; set; } - public int PointsAwarded { get; set; } + public double PointsAwarded { get; set; } public int MaxPoints { get; set; } public string UserResponse { get; set; } = string.Empty; public string AiFeedback { get; set; } = string.Empty; diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Tests/TestResultDto.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Tests/TestResultDto.cs index ad4e767..5f1e138 100644 --- a/backend/SkillProof/SkillProof.Entities/Dtos/Tests/TestResultDto.cs +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Tests/TestResultDto.cs @@ -6,7 +6,7 @@ public class TestResultDto { public string TestId { get; set; } = string.Empty; public string JobApplicationId { get; set; } = string.Empty; - public int Score { get; set; } + public double Score { get; set; } public int MaxScore { get; set; } public bool Passed { get; set; } public DifficultyLevel DifficultyLevel { get; set; } diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Tests/TestSubmitSkillDto.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Tests/TestSubmitSkillDto.cs new file mode 100644 index 0000000..8e7b4e9 --- /dev/null +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Tests/TestSubmitSkillDto.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SkillProof.Entities.Dtos.Tests +{ + public class TestSubmitSkillDto + { + public string SkillId { get; set; } = string.Empty; + public string AssessmentId { get; set; } + public List Answers { get; set; } = new(); + } +} diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Tests/UserTestReviewDto.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Tests/UserTestReviewDto.cs new file mode 100644 index 0000000..90528dc --- /dev/null +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Tests/UserTestReviewDto.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using SkillProof.Entities.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SkillProof.Entities.Dtos.Tests +{ + public class UserTestReviewDto + { + public string QuestionId { get; set; } + public string TestAnswerId { get; set; } + public double Score { get; set; } + public string QuestionText { get; set; } + public string UserResponse { get; set; } + public bool Inspected { get; set; } + public QuestionType QuestionType { get; set; } + + //public string? AiFeedback { get; set; } + + public string UserId { get; set; } + } +} diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Tests/UserTestsDto.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Tests/UserTestsDto.cs new file mode 100644 index 0000000..36c87f0 --- /dev/null +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Tests/UserTestsDto.cs @@ -0,0 +1,15 @@ +using SkillProof.Entities.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SkillProof.Entities.Dtos.Tests +{ + public class UserTestsDto + { + public DifficultyLevel DifficultyLevel { get; set; } + public bool Passed { get; set; } + } +} diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Users/BadgeDto.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Users/BadgeDto.cs new file mode 100644 index 0000000..ff788d3 --- /dev/null +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Users/BadgeDto.cs @@ -0,0 +1,16 @@ +using SkillProof.Entities.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SkillProof.Entities.Dtos.Users +{ + public class BadgeDto + { + public string SourceName { get; set; } = string.Empty; + public DifficultyLevel DifficultyLevel { get; set; } + public DateTime IssuedAt { get; set; } + } +} diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Users/UpdateSkillToUser.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Users/UpdateSkillToUser.cs new file mode 100644 index 0000000..37cb2b7 --- /dev/null +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Users/UpdateSkillToUser.cs @@ -0,0 +1,16 @@ +using SkillProof.Entities.Dtos.Skill; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SkillProof.Entities.Dtos.Users +{ + public class UpdateSkillToUser + { + public string skillId { get; set; } = string.Empty; + public string userId { get; set; } = string.Empty; + public List Skills { get; set; } = new List(); + } +} diff --git a/backend/SkillProof/SkillProof.Entities/Dtos/Users/ViewUser.cs b/backend/SkillProof/SkillProof.Entities/Dtos/Users/ViewUser.cs index 259167d..7eea698 100644 --- a/backend/SkillProof/SkillProof.Entities/Dtos/Users/ViewUser.cs +++ b/backend/SkillProof/SkillProof.Entities/Dtos/Users/ViewUser.cs @@ -1,4 +1,6 @@ -namespace SkillProof.Entities.Dtos.Users +using SkillProof.Entities.Dtos.Skill; + +namespace SkillProof.Entities.Dtos.Users { public class ViewUser { @@ -9,5 +11,12 @@ public class ViewUser public string Headline { get; set; } = string.Empty; public string Bio { get; set; } = string.Empty; public string? CompanyId { get; set; } + + public List? Skills { get; set; } + public List SavedJobIds { get; set; } = new List(); + public List AppliedJobIds { get; set; } = new List(); + public List Badges { get; set; } = new List(); + public List Educations { get; set; } = new List(); + public List Experiences { get; set; } = new List(); } } diff --git a/backend/SkillProof/SkillProof.Entities/Models/Assesments.cs b/backend/SkillProof/SkillProof.Entities/Models/Assesments.cs index ee6a262..333d2c6 100644 --- a/backend/SkillProof/SkillProof.Entities/Models/Assesments.cs +++ b/backend/SkillProof/SkillProof.Entities/Models/Assesments.cs @@ -25,6 +25,10 @@ public class Assessments : IIdentity public bool IsActive { get; set; } = true; + public string? SkillId { get; set; } = null; + + public virtual SkillModel? Skill { get; set; } = null; //???? + public virtual ICollection Questions { get; set; } = new List(); public virtual ICollection Jobs { get; set; } = new List(); public virtual ICollection TestAttempts { get; set; } = new List(); diff --git a/backend/SkillProof/SkillProof.Entities/Models/Education.cs b/backend/SkillProof/SkillProof.Entities/Models/Education.cs new file mode 100644 index 0000000..2a22e93 --- /dev/null +++ b/backend/SkillProof/SkillProof.Entities/Models/Education.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SkillProof.Entities.Helper; + +namespace SkillProof.Entities.Models +{ + public class Education : IIdentity + { + [Key] + public string Id { get; set; } = Guid.NewGuid().ToString(); + + [ForeignKey("User")] + public string UserId { get; set; } + + [Required] + public string School { get; set; } + + public string Degree { get; set; } + + public string FieldOfStudy { get; set; } + + [Required] + public DateTime StartDate { get; set; } + + public DateTime? EndDate { get; set; } + + public bool IsOngoing => EndDate == null; + + public string Description { get; set; } + + public virtual Users User { get; set; } + } +} diff --git a/backend/SkillProof/SkillProof.Entities/Models/Gemini/GradingRequest.cs b/backend/SkillProof/SkillProof.Entities/Models/Gemini/GradingRequest.cs new file mode 100644 index 0000000..68830e2 --- /dev/null +++ b/backend/SkillProof/SkillProof.Entities/Models/Gemini/GradingRequest.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SkillProof.Entities.Models.Gemini +{ + public class GradingRequest + { + public string Question { get; set; } + public string StudentAnswer { get; set; } + + public string AnswerToQuestion { get; set; } + } +} diff --git a/backend/SkillProof/SkillProof.Entities/Models/Gemini/GradingResult.cs b/backend/SkillProof/SkillProof.Entities/Models/Gemini/GradingResult.cs new file mode 100644 index 0000000..bc78b23 --- /dev/null +++ b/backend/SkillProof/SkillProof.Entities/Models/Gemini/GradingResult.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SkillProof.Entities.Models.Gemini +{ + public class GradingResult + { + public double Score { get; set; } + } +} diff --git a/backend/SkillProof/SkillProof.Entities/Models/Job.cs b/backend/SkillProof/SkillProof.Entities/Models/Job.cs index 5325abc..685c707 100644 --- a/backend/SkillProof/SkillProof.Entities/Models/Job.cs +++ b/backend/SkillProof/SkillProof.Entities/Models/Job.cs @@ -19,8 +19,12 @@ public class Job: IIdentity [Required] public string Description { get; set; } + [Required] + public string ShortDescription { get; set; } + [Required] public string Location { get; set; } + public int Salary { get; set; } public string Tags { get; set; } // Json array of strings diff --git a/backend/SkillProof/SkillProof.Entities/Models/JobApplication.cs b/backend/SkillProof/SkillProof.Entities/Models/JobApplication.cs index 9ec45b8..058ffa0 100644 --- a/backend/SkillProof/SkillProof.Entities/Models/JobApplication.cs +++ b/backend/SkillProof/SkillProof.Entities/Models/JobApplication.cs @@ -22,6 +22,8 @@ public class JobApplication: IIdentity [Required] public JobApplicationStatus Status { get; set; } + public bool IsRead { get; set; } = false; + public DateTime AppliedAt { get; set; } = DateTime.UtcNow; public virtual Job Job { get; set; } diff --git a/backend/SkillProof/SkillProof.Entities/Models/SkillModel.cs b/backend/SkillProof/SkillProof.Entities/Models/SkillModel.cs new file mode 100644 index 0000000..51e924e --- /dev/null +++ b/backend/SkillProof/SkillProof.Entities/Models/SkillModel.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Security.Principal; +using System.Text; +using System.Threading.Tasks; +using SkillProof.Entities; +using SkillProof.Entities.Helper; + +namespace SkillProof.Entities.Models +{ + public class SkillModel: Helper.IIdentity + { + [Key] + public string Id { get; set; } + + [Required] + [MaxLength(100)] + public string Name { get; set; } + + // Egy skillhez több assessment + public virtual ICollection Assessments { get; set; } + = new List(); + + // Több usernek lehet ugyanaz a skillje + public virtual ICollection Users { get; set; } + = new List(); + + public SkillModel() + { + Id = Guid.NewGuid().ToString(); + } + } +} diff --git a/backend/SkillProof/SkillProof.Entities/Models/TestAnswers.cs b/backend/SkillProof/SkillProof.Entities/Models/TestAnswers.cs index d923093..1797a4d 100644 --- a/backend/SkillProof/SkillProof.Entities/Models/TestAnswers.cs +++ b/backend/SkillProof/SkillProof.Entities/Models/TestAnswers.cs @@ -19,7 +19,15 @@ public class TestAnswers: IIdentity [Required] public bool IsCorrect { get; set; } + + [Required] + public double Score { get; set; } + public string AiFeedback { get; set; } + public string? ManualFeedback { get; set; } + + [Required] + public bool Inspected { get; set; } public virtual Questions Question { get; set; } public virtual Tests Test { get; set; } diff --git a/backend/SkillProof/SkillProof.Entities/Models/Tests.cs b/backend/SkillProof/SkillProof.Entities/Models/Tests.cs index f4ba96a..f09681d 100644 --- a/backend/SkillProof/SkillProof.Entities/Models/Tests.cs +++ b/backend/SkillProof/SkillProof.Entities/Models/Tests.cs @@ -21,7 +21,7 @@ public class Tests: IIdentity public bool Passed { get; set; } [Required] - public int Score { get; set; } + public double Score { get; set; } public DateTime CompletedAt { get; set; } = DateTime.UtcNow; public virtual JobApplication? JobApplication { get; set; } diff --git a/backend/SkillProof/SkillProof.Entities/Models/Users.cs b/backend/SkillProof/SkillProof.Entities/Models/Users.cs index 0c7b2a9..12f8e4e 100644 --- a/backend/SkillProof/SkillProof.Entities/Models/Users.cs +++ b/backend/SkillProof/SkillProof.Entities/Models/Users.cs @@ -21,11 +21,15 @@ public class Users: IdentityUser, IIdentity public string Bio { get; set; } public DateTime CreatedAt { get; set; } public string? CompanyId { get; set; } - public string? CompanyRole { get; set; } + public string? CompanyRole { get; set; } public virtual Companies Companies { get; set; } public virtual ICollection? UserExperiences { get; set; } + public virtual ICollection Educations { get; set; } = new List(); public virtual ICollection? Tests { get; set; } public virtual ICollection? JobApplications { get; set; } + public virtual ICollection SavedJobs { get; set; } = new List(); + + public virtual ICollection Skills { get; set; } = new List(); public Users() { diff --git a/backend/SkillProof/SkillProof.Logic/Assesments/AssesmentLogic.cs b/backend/SkillProof/SkillProof.Logic/Assesments/AssesmentLogic.cs index 6d1c095..c2c6fb5 100644 --- a/backend/SkillProof/SkillProof.Logic/Assesments/AssesmentLogic.cs +++ b/backend/SkillProof/SkillProof.Logic/Assesments/AssesmentLogic.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using SkillProof.Data; using SkillProof.Data.Repositorys; using SkillProof.Entities.Dtos.Assesment; using SkillProof.Entities.Dtos.Questions; @@ -12,15 +13,21 @@ public class AssessmentLogic : IAssessmentLogic private readonly IRepository _assessmentRepository; private readonly IRepository _questionRepository; private readonly IRepository _jobRepository; + private readonly IRepository _skillRepository; + private readonly SkillProofDbContext _ctx; public AssessmentLogic( IRepository assessmentRepository, IRepository questionRepository, - IRepository jobRepository) + IRepository jobRepository, + IRepository skillRepository, + SkillProofDbContext ctx) { _assessmentRepository = assessmentRepository; _questionRepository = questionRepository; _jobRepository = jobRepository; + _skillRepository = skillRepository; + _ctx = ctx; } public async Task CreateAssessmentAsync(CreateAssessmentDto model, string userId) @@ -180,5 +187,30 @@ public async Task AssignAssessmentToJob(string assessmentId, string jobId) await _assessmentRepository.Update(assessment); } + + public async Task AddAssessmentToSkill(AddAssesmentsToSkillDto dto) + { + var skill = await _skillRepository.GetOne(dto.SkillId); + if (skill == null) + { + throw new KeyNotFoundException("Skill not found."); + + } + foreach (var assessmentId in dto.AssessmentIds) + { + var assessment = await _assessmentRepository.GetOne(assessmentId); + if (assessment == null) + { + throw new KeyNotFoundException($"Assessment with ID {assessmentId} not found."); + } + skill.Assessments.Add(assessment); + } + await _skillRepository.Update(skill); + } + + public Task> GetAssessmentBySkill(string skillId) + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/backend/SkillProof/SkillProof.Logic/Assesments/IAssessmentLogic.cs b/backend/SkillProof/SkillProof.Logic/Assesments/IAssessmentLogic.cs index 6c95df6..a86ca72 100644 --- a/backend/SkillProof/SkillProof.Logic/Assesments/IAssessmentLogic.cs +++ b/backend/SkillProof/SkillProof.Logic/Assesments/IAssessmentLogic.cs @@ -10,6 +10,7 @@ public interface IAssessmentLogic Task GetAssessmentByIdAsync(string id); Task UpdateAssessmentAsync(string id, UpdateAssessmentDto model, string userId); Task DeleteAssessmentAsync(string id); - Task AssignAssessmentToJob(string assessmentId, string jobId); + Task AddAssessmentToSkill(AddAssesmentsToSkillDto dto); + Task> GetAssessmentBySkill(string skillId); } \ No newline at end of file diff --git a/backend/SkillProof/SkillProof.Logic/Education/EducationLogic.cs b/backend/SkillProof/SkillProof.Logic/Education/EducationLogic.cs new file mode 100644 index 0000000..3bb8f3b --- /dev/null +++ b/backend/SkillProof/SkillProof.Logic/Education/EducationLogic.cs @@ -0,0 +1,86 @@ +using Microsoft.EntityFrameworkCore; +using SkillProof.Data.Repositorys; +using SkillProof.Entities.Dtos.Education; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SkillProof.Entities.Models; + +namespace SkillProof.Logic.Education +{ + public class EducationLogic : IEducationLogic + { + private readonly IRepository _educationRepository; + private readonly IRepository _userRepository; + + public EducationLogic(IRepository educationRepository, IRepository userRepository) + { + _educationRepository = educationRepository; + _userRepository = userRepository; + } + public async Task DeleteEducationAsync(string id, string userId) + { + var education = await _educationRepository.GetOne(id); + if (education == null) + { + throw new Exception("Education not found"); + } + var user = await _userRepository.GetOne(userId); + if (user == null) + { + throw new Exception("User not found"); + } + await _educationRepository.DeleteById(id); + } + + public async Task> GetEducationsByUserIdAsync(string userId) + { + var user = await _userRepository.GetOne(userId); + if (user == null) + { + throw new Exception("User not found"); + } + var educations = await _educationRepository.GetAll() + .Where(e => e.UserId == userId) + .Select(e => new EducationViewDto + { + Id = e.Id, + School = e.School, + Degree = e.Degree, + StartDate = e.StartDate, + EndDate = e.EndDate, + Description = e.Description, + }).ToListAsync(); + return educations; + } + + public async Task CreateEducationAsync(EducationCreateDto entity, string userId) + { + var user = await _userRepository.GetOne(userId); + if (user == null) + { + throw new Exception("User not found"); + } + if (entity.StartDate < new DateTime(1753, 1, 1)) + { + throw new Exception("StartDate must be a valid date."); + } + + var education = new SkillProof.Entities.Models.Education + { + UserId = userId, + School = entity.School, + Degree = entity.Degree, + FieldOfStudy = entity.FieldOfStudy, + StartDate = entity.StartDate, + EndDate = entity.EndDate, + Description = entity.Description + }; + + await _educationRepository.Create(education); + return entity; + } + } +} \ No newline at end of file diff --git a/backend/SkillProof/SkillProof.Logic/Education/IEducationLogic.cs b/backend/SkillProof/SkillProof.Logic/Education/IEducationLogic.cs new file mode 100644 index 0000000..197854c --- /dev/null +++ b/backend/SkillProof/SkillProof.Logic/Education/IEducationLogic.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SkillProof.Entities.Dtos.Education; + +namespace SkillProof.Logic.Education +{ + public interface IEducationLogic + { + Task> GetEducationsByUserIdAsync(string userId); + Task DeleteEducationAsync(string id, string userId); + Task CreateEducationAsync(EducationCreateDto entity, string userId); + } +} diff --git a/backend/SkillProof/SkillProof.Logic/Experience/ExperienceLogic.cs b/backend/SkillProof/SkillProof.Logic/Experience/ExperienceLogic.cs new file mode 100644 index 0000000..4d2ad84 --- /dev/null +++ b/backend/SkillProof/SkillProof.Logic/Experience/ExperienceLogic.cs @@ -0,0 +1,85 @@ +using Microsoft.EntityFrameworkCore; +using SkillProof.Data.Repositorys; +using SkillProof.Entities.Dtos.Experience; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using SkillProof.Entities.Models; + +namespace SkillProof.Logic.Experience +{ + public class ExperienceLogic : IExperienceLogic + { + private readonly IRepository _experienceRepository; + private readonly IRepository _userRepository; + + public ExperienceLogic(IRepository experienceRepository, IRepository userRepository) + { + _experienceRepository = experienceRepository; + _userRepository = userRepository; + } + + public async Task DeleteExperienceAsync(string id, string userId) + { + var experience = await _experienceRepository.GetOne(id); + if (experience == null) + { + throw new Exception("Experience not found"); + } + + var user = await _userRepository.GetOne(userId); + if (user == null) + { + throw new Exception("User not found"); + } + + await _experienceRepository.DeleteById(id); + } + + public async Task> GetExperiencesByUserIdAsync(string userId) + { + var user = await _userRepository.GetOne(userId); + if (user == null) + { + throw new Exception("User not found"); + } + + var experiences = await _experienceRepository.GetAll() + .Where(e => e.UserId == userId) + .Select(e => new ExperienceViewDto + { + Id = e.Id, + CompanyName = e.CompanyName, + JobTitle = e.JobTitle, + StartDate = e.StartDate, + EndDate = e.EndDate + }) + .ToListAsync(); + + return experiences; + } + + public async Task CreateExperienceAsync(ExperienceCreateDto entity, string userId) + { + var user = await _userRepository.GetOne(userId); + if (user == null) + { + throw new Exception("User not found"); + } + + var experience = new UserExperiences + { + UserId = userId, + CompanyName = entity.CompanyName, + JobTitle = entity.JobTitle, + StartDate = entity.StartDate, + EndDate = entity.EndDate + }; + + await _experienceRepository.Create(experience); + + return entity; + } + } +} \ No newline at end of file diff --git a/backend/SkillProof/SkillProof.Logic/Experience/IExperienceLogic.cs b/backend/SkillProof/SkillProof.Logic/Experience/IExperienceLogic.cs new file mode 100644 index 0000000..b04e963 --- /dev/null +++ b/backend/SkillProof/SkillProof.Logic/Experience/IExperienceLogic.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SkillProof.Entities.Dtos.Experience; + +namespace SkillProof.Logic.Experience +{ + public interface IExperienceLogic + { + Task> GetExperiencesByUserIdAsync(string userId); + Task DeleteExperienceAsync(string id, string userId); + Task CreateExperienceAsync(ExperienceCreateDto entity, string userId); + } +} diff --git a/backend/SkillProof/SkillProof.Logic/Gemini/GeminiService.cs b/backend/SkillProof/SkillProof.Logic/Gemini/GeminiService.cs new file mode 100644 index 0000000..e9f265b --- /dev/null +++ b/backend/SkillProof/SkillProof.Logic/Gemini/GeminiService.cs @@ -0,0 +1,90 @@ +using Google.GenAI; +using Google.GenAI.Types; +using Microsoft.Extensions.Configuration; +using SkillProof.Entities.Models.Gemini; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net.Http.Json; +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace SkillProof.Logic.Gemini +{ + public class GeminiService : IGeminiService + { + private readonly Client _client; + private readonly IConfiguration _config; + private static readonly SemaphoreSlim _rateLimiter = new SemaphoreSlim(1, 1); + + public GeminiService(IConfiguration config) + { + string apiKey = config["Gemini:ApiKey"]; + if (!string.IsNullOrEmpty(apiKey)) + { + apiKey = new string(apiKey.Where(c => c < 128 && !char.IsWhiteSpace(c)).ToArray()); + } + + _client = new Client(apiKey: apiKey); + } + + public async Task EvaluateAnswerAsync(GradingRequest request) + { + await _rateLimiter.WaitAsync(); + + try + { + var prompt = $@" + You are an expert strict exam evaluator. + Evaluate the user's answer to the following question and if there is an answer provided then based on what the inputted Answer to the question is. + Score the answer as 0 (completely wrong), 0.5 (partially correct), or 1 (perfect). + Respond ONLY with the number (0, 0.5, or 1). No explanation, no extra text. + + Question: {request.Question} + Answer to the question: {request.AnswerToQuestion} + Student Answer: {request.StudentAnswer}"; + + var config = new GenerateContentConfig + { + ResponseMimeType = "text/plain" + }; + + var response = await _client.Models.GenerateContentAsync( + model: "gemini-2.5-flash", + contents: prompt, + config: config + ); + + await Task.Delay(TimeSpan.FromSeconds(4)); + + if (double.TryParse(response.Text, out double parsedScore)) + { + return parsedScore; + } + + return 0.0; + } + finally + { + _rateLimiter.Release(); + } + } + + public async Task> EvalueateAllAsync(List requests) + { + var answers = new List(); + foreach (var request in requests) + { + double score = await EvaluateAnswerAsync(request); + answers.Add(score); + } + return answers; + } + } +} + + diff --git a/backend/SkillProof/SkillProof.Logic/Gemini/IGeminiService.cs b/backend/SkillProof/SkillProof.Logic/Gemini/IGeminiService.cs new file mode 100644 index 0000000..8b86f79 --- /dev/null +++ b/backend/SkillProof/SkillProof.Logic/Gemini/IGeminiService.cs @@ -0,0 +1,14 @@ +using SkillProof.Entities.Models.Gemini; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SkillProof.Logic.Gemini +{ + public interface IGeminiService + { + Task EvaluateAnswerAsync(GradingRequest request); + } +} diff --git a/backend/SkillProof/SkillProof.Logic/Helper/DbInitializer.cs b/backend/SkillProof/SkillProof.Logic/Helper/DbInitializer.cs index f4b2ec8..f78f063 100644 --- a/backend/SkillProof/SkillProof.Logic/Helper/DbInitializer.cs +++ b/backend/SkillProof/SkillProof.Logic/Helper/DbInitializer.cs @@ -1,10 +1,7 @@ +using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using SkillProof.Entities.Enums; using SkillProof.Entities.Models; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; @@ -13,52 +10,126 @@ namespace SkillProof.Data public class QuestionSeedDto { public QuestionType Type { get; set; } - public string Language { get; set; } + public string? Language { get; set; } public DifficultyLevel Difficulty { get; set; } - public string Title { get; set; } - public string QuestionText { get; set; } + public string Title { get; set; } = string.Empty; + public string QuestionText { get; set; } = string.Empty; + public List Tags { get; set; } = new(); public TrueFalseSeedDto? TrueFalsePayload { get; set; } + public TrueFalseSeedDto? TrueFalse { get; set; } public MultipleChoiceSeedDto? MultipleChoicePayload { get; set; } + public MultipleChoiceSeedDto? MultipleChoice { get; set; } + public FillInTheBlankSeedDto? FillInTheBlankPayload { get; set; } + public FillInTheBlankSeedDto? FillInTheBlank { get; set; } + public CodeCompletionSeedDto? CodeCompletionPayload { get; set; } + public CodeCompletionSeedDto? CodeCompletion { get; set; } } public class TrueFalseSeedDto { public bool CorrectAnswer { get; set; } - public string Explanation { get; set; } + public string? Explanation { get; set; } } public class MultipleChoiceSeedDto { - public object Options { get; set; } - public object CorrectAnswerIds { get; set; } + public List Options { get; set; } = new(); + public List? CorrectAnswerIds { get; set; } + public List? CorrectOptionIndexes { get; set; } public bool AllowMultipleSelection { get; set; } } + public class FillInTheBlankSeedDto + { + public string Answer { get; set; } = string.Empty; + public string? ManualFeedback { get; set; } + } + + public class CodeCompletionSeedDto + { + public string CodeSnippet { get; set; } = string.Empty; + public List AcceptedAnswers { get; set; } = new(); + } + public static class DbInitializer { + private const string SeedAssessmentTitle = "Full-Stack Developer Starter Kit"; + private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web); + public static void Seed(DbContext context) { - context.Database.EnsureCreated(); + var existingSeed = context.Set() + .Include(a => a.Questions) + .ThenInclude(q => q.MultipleChoiceQuestion) + .Include(a => a.Questions) + .ThenInclude(q => q.CodeCompletionQuestion) + .Include(a => a.Questions) + .ThenInclude(q => q.FillInTheBlankQuestions) + .Include(a => a.Questions) + .ThenInclude(q => q.TrueFalseQuestion) + .FirstOrDefault(a => a.Title == SeedAssessmentTitle); + + var adminRole = context.Set().FirstOrDefault(r => r.NormalizedName == "ADMIN"); + + if (adminRole == null) + { + adminRole = new IdentityRole + { + Id = Guid.NewGuid().ToString(), + Name = "Admin", + NormalizedName = "ADMIN" + }; + context.Set().Add(adminRole); + } - if (context.Set().Any() || context.Set().Any()) + var existingUser = context.Set().FirstOrDefault(u => u.NormalizedUserName == "SYSTEMADMIN"); + string adminUserId; + + if (existingUser != null) + { + adminUserId = existingUser.Id; + } + else { - return; + var newUser = new Users + { + Id = Guid.NewGuid().ToString(), + UserName = "systemadmin", + Email = "admin@system.local", + NormalizedUserName = "SYSTEMADMIN", + NormalizedEmail = "ADMIN@SYSTEM.LOCAL", + EmailConfirmed = true, + SecurityStamp = Guid.NewGuid().ToString() + }; + + var passwordHasher = new PasswordHasher(); + newUser.PasswordHash = passwordHasher.HashPassword(newUser, "AdminPassword123!"); + + context.Set().Add(newUser); + adminUserId = newUser.Id; } - var existingUser = context.Set().FirstOrDefault(); - var adminUserId = existingUser != null ? existingUser.Id : Guid.NewGuid().ToString(); + var userRoleExists = context.Set>() + .Any(ur => ur.UserId == adminUserId && ur.RoleId == adminRole.Id); + + if (!userRoleExists) + { + context.Set>().Add(new IdentityUserRole + { + UserId = adminUserId, + RoleId = adminRole.Id + }); + } - // Debugging tipp: kiírathatod az elérési utat a konzolra, ha továbbra sem találja var seedFilePath = Path.Combine(AppContext.BaseDirectory, "seed-questions.json"); if (!File.Exists(seedFilePath)) { - // Ha nem találja a bin-ben, megpróbáljuk a gyökérben (fejlesztői környezet) seedFilePath = Path.Combine(Directory.GetCurrentDirectory(), "seed-questions.json"); if (!File.Exists(seedFilePath)) { - throw new FileNotFoundException($"The seed JSON file was not found. Looked in: {seedFilePath}"); + throw new FileNotFoundException($"The seed JSON file was not found. Searched path: {seedFilePath}"); } } @@ -70,66 +141,165 @@ public static void Seed(DbContext context) if (seedQuestions == null || !seedQuestions.Any()) { - return; + throw new InvalidDataException("The seed JSON file was parsed but contains no valid questions."); } - var generatedQuestions = new List(); + var seedAssessment = existingSeed ?? new Assessments + { + Id = Guid.NewGuid().ToString(), + Title = SeedAssessmentTitle, + Description = "Comprehensive test covering basic C# and .NET concepts.", + DifficultyLevel = DifficultyLevel.Junior, + CreatedBy = adminUserId, + CreatedAt = DateTime.UtcNow, + IsActive = true + }; + + seedAssessment.Title = SeedAssessmentTitle; + seedAssessment.Description = "Comprehensive test covering basic C# and .NET concepts."; + seedAssessment.DifficultyLevel = DifficultyLevel.Junior; + seedAssessment.CreatedBy = adminUserId; + seedAssessment.IsActive = true; + + if (existingSeed == null) + { + context.Set().Add(seedAssessment); + } foreach (var sq in seedQuestions) { - var questionId = Guid.NewGuid().ToString(); - var question = new Questions + var question = seedAssessment.Questions + .FirstOrDefault(q => q.Title == sq.Title && q.Type == sq.Type); + + if (question == null) { - Id = questionId, - Type = sq.Type, - Language = sq.Language, - Difficulty = sq.Difficulty, - Title = sq.Title, - QuestionText = sq.QuestionText, - CreatedBy = adminUserId, - CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow, - IsActive = true - }; + question = new Questions + { + Id = Guid.NewGuid().ToString(), + Type = sq.Type, + CreatedAt = DateTime.UtcNow + }; + + context.Set().Add(question); + seedAssessment.Questions.Add(question); + } + + question.Type = sq.Type; + question.Language = NormalizeLanguage(sq.Language); + question.Difficulty = sq.Difficulty; + question.Title = sq.Title; + question.QuestionText = sq.QuestionText; + question.Tags = NormalizeTags(sq.Tags); + question.CreatedBy = adminUserId; + question.UpdatedAt = DateTime.UtcNow; + question.IsActive = true; + + UpsertTypedPayload(context, question, sq); + } + + context.SaveChanges(); + } + + private static void UpsertTypedPayload(DbContext context, Questions question, QuestionSeedDto seed) + { + switch (seed.Type) + { + case QuestionType.TrueFalse: + { + var payload = seed.TrueFalse ?? seed.TrueFalsePayload + ?? throw new InvalidDataException($"Missing true/false payload for seed question '{seed.Title}'."); + + var entity = question.TrueFalseQuestion + ?? context.Set().Find(question.Id) + ?? new TrueFalseQuestions { QuestionId = question.Id }; - generatedQuestions.Add(question); - context.Set().Add(question); + entity.CorrectAnswer = payload.CorrectAnswer; + entity.Explanation = payload.Explanation; - if (sq.Type == QuestionType.TrueFalse && sq.TrueFalsePayload != null) + if (context.Entry(entity).State == EntityState.Detached) + { + context.Set().Add(entity); + } + break; + } + case QuestionType.MultipleChoice: { - context.Set().Add(new TrueFalseQuestions + var payload = seed.MultipleChoice ?? seed.MultipleChoicePayload + ?? throw new InvalidDataException($"Missing multiple-choice payload for seed question '{seed.Title}'."); + + var entity = question.MultipleChoiceQuestion + ?? context.Set().Find(question.Id) + ?? new MultipleChoiceQuestions { QuestionId = question.Id }; + + entity.Options = JsonSerializer.Serialize(payload.Options, JsonOptions); + entity.CorrectAnswerIds = JsonSerializer.Serialize(payload.CorrectOptionIndexes ?? payload.CorrectAnswerIds ?? new List(), JsonOptions); + entity.AllowMultipleSelection = payload.AllowMultipleSelection; + + if (context.Entry(entity).State == EntityState.Detached) { - QuestionId = questionId, - CorrectAnswer = sq.TrueFalsePayload.CorrectAnswer, - Explanation = sq.TrueFalsePayload.Explanation - }); + context.Set().Add(entity); + } + break; } - else if (sq.Type == QuestionType.MultipleChoice && sq.MultipleChoicePayload != null) + case QuestionType.FillInTheBlank: { - context.Set().Add(new MultipleChoiceQuestions + var payload = seed.FillInTheBlank ?? seed.FillInTheBlankPayload + ?? throw new InvalidDataException($"Missing fill-in-the-blank payload for seed question '{seed.Title}'."); + + var entity = question.FillInTheBlankQuestions + ?? context.Set().Find(question.Id) + ?? new FillInTheBlankQuestions { QuestionId = question.Id }; + + entity.Answer = payload.Answer; + entity.manualFeedback = payload.ManualFeedback; + + if (context.Entry(entity).State == EntityState.Detached) { - QuestionId = questionId, - Options = JsonSerializer.Serialize(sq.MultipleChoicePayload.Options), - CorrectAnswerIds = JsonSerializer.Serialize(sq.MultipleChoicePayload.CorrectAnswerIds), - AllowMultipleSelection = sq.MultipleChoicePayload.AllowMultipleSelection - }); + context.Set().Add(entity); + } + break; } + case QuestionType.CodeCompletion: + { + var payload = seed.CodeCompletion ?? seed.CodeCompletionPayload + ?? throw new InvalidDataException($"Missing code-completion payload for seed question '{seed.Title}'."); + + var entity = question.CodeCompletionQuestion + ?? context.Set().Find(question.Id) + ?? new CodeCompletionQuestions { QuestionId = question.Id }; + + entity.CodeSnippet = payload.CodeSnippet; + entity.AcceptedAnswers = JsonSerializer.Serialize(payload.AcceptedAnswers, JsonOptions); + + if (context.Entry(entity).State == EntityState.Detached) + { + context.Set().Add(entity); + } + break; + } + default: + throw new InvalidDataException($"Unsupported seed question type '{seed.Type}' for '{seed.Title}'."); } + } - var defaultAssessment = new Assessments + private static string NormalizeLanguage(string? language) + { + if (string.IsNullOrWhiteSpace(language)) { - Id = Guid.NewGuid().ToString(), - Title = "Full-Stack Developer Starter Kit", - Description = "Comprehensive test covering basic C# and .NET concepts.", - DifficultyLevel = DifficultyLevel.Junior, - CreatedBy = adminUserId, - CreatedAt = DateTime.UtcNow, - IsActive = true, - Questions = generatedQuestions - }; + return "General"; + } - context.Set().Add(defaultAssessment); - context.SaveChanges(); + var trimmed = language.Trim(); + return trimmed.Length > 20 ? trimmed[..20] : trimmed; + } + + private static List NormalizeTags(List? tags) + { + return tags? + .Where(tag => !string.IsNullOrWhiteSpace(tag)) + .Select(tag => tag.Trim()) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList() ?? new List(); } } -} \ No newline at end of file +} diff --git a/backend/SkillProof/SkillProof.Logic/Jobs/IJobLogic.cs b/backend/SkillProof/SkillProof.Logic/Jobs/IJobLogic.cs index 603c075..22a2d7d 100644 --- a/backend/SkillProof/SkillProof.Logic/Jobs/IJobLogic.cs +++ b/backend/SkillProof/SkillProof.Logic/Jobs/IJobLogic.cs @@ -20,4 +20,14 @@ public interface IJobLogic Task GetCandidateTestForJob(string jobId); + Task> GetJobsOfCompanyAsync(string currentUserId); + + Task ApplyForJobAsync(string jobId, string userId); + Task AcceptCandidateAsync(string userId, string jobId); + + Task RejectCandidateAsync(string userId, string jobId); + + Task> GetNotificationsAsync(string userId); + + Task MarkNotificationAsReadAsync(string applicationId, string userId); } \ No newline at end of file diff --git a/backend/SkillProof/SkillProof.Logic/Jobs/JobLogic.cs b/backend/SkillProof/SkillProof.Logic/Jobs/JobLogic.cs index c88a5a8..3ce62db 100644 --- a/backend/SkillProof/SkillProof.Logic/Jobs/JobLogic.cs +++ b/backend/SkillProof/SkillProof.Logic/Jobs/JobLogic.cs @@ -50,6 +50,7 @@ public async Task CreateJobAsync(JobCreateDto model, string companyI CompanyId = companyId, Title = model.Title, Description = _markdownService.ToHtml(model.Description), + ShortDescription = model.ShortDescription, Location = model.Location, Tags = model.Tags, EmploymentType = model.EmploymentType, @@ -75,6 +76,7 @@ public async Task CreateJobAsync(JobCreateDto model, string companyI CompanyId = newJob.CompanyId, Title = newJob.Title, Description = newJob.Description, + ShortDescription = newJob.ShortDescription, Location = newJob.Location, Tags = newJob.Tags, EmploymentType = newJob.EmploymentType, @@ -94,6 +96,7 @@ public async Task> GetAllJobsAsync() CompanyId = j.CompanyId, Title = j.Title, Description = j.Description, + ShortDescription = j.ShortDescription, Location = j.Location, Tags = j.Tags, EmploymentType = j.EmploymentType, @@ -139,10 +142,12 @@ public async Task> GetAllJobsAsync() CompanyId = job.CompanyId, Title = job.Title, Description = job.Description, + ShortDescription = job.ShortDescription, Location = job.Location, Tags = job.Tags, EmploymentType = job.EmploymentType, CreatedAt = job.CreatedAt, + Salary = job.Salary, Id = job.Id, AssessmentIds = job.Assessments.Select(a => a.Id).ToList(), Assessments = job.Assessments.Select(a => new AssessmentViewDto @@ -183,6 +188,7 @@ public async Task> GetJobsByCompanyIdAsync(string compan CompanyId = j.CompanyId, Title = j.Title, Description = j.Description, + ShortDescription = j.ShortDescription, Location = j.Location, Tags = j.Tags, EmploymentType = j.EmploymentType, @@ -213,6 +219,12 @@ public async Task> GetJobsByCompanyIdAsync(string compan public async Task UpdateJobAsync(string id, JobViewDto model, string companyId) { + + if (model == null) + { + throw new ArgumentNullException(nameof(model), "Model cannot be null."); + } + var job = await _jobRepository.GetAll() .Include(j => j.Assessments) .FirstOrDefaultAsync(j => j.Id == id); @@ -229,9 +241,11 @@ public async Task UpdateJobAsync(string id, JobViewDto model, string job.Title = model.Title; job.Description = model.Description; + job.ShortDescription = model.ShortDescription; job.Location = model.Location; job.Tags = model.Tags; job.EmploymentType = model.EmploymentType; + job.Salary = model.Salary; job.Assessments.Clear(); @@ -251,15 +265,23 @@ public async Task UpdateJobAsync(string id, JobViewDto model, string return new JobViewDto { + Id = job.Id, CompanyId = job.CompanyId, Title = job.Title, Description = job.Description, + ShortDescription = job.ShortDescription, Location = job.Location, Tags = job.Tags, EmploymentType = job.EmploymentType, + Salary = job.Salary, CreatedAt = job.CreatedAt, - Id = job.Id, - AssessmentIds = job.Assessments.Select(a => a.Id).ToList() + AssessmentIds = job.Assessments.Select(a => a.Id).ToList(), + Assessments = job.Assessments.Select(a => new AssessmentViewDto + { + Id = a.Id, + Title = a.Title, + DifficultyLevel = a.DifficultyLevel + }).ToList() }; } @@ -397,7 +419,11 @@ public async Task> GetTestToJob(string id) return null; } - var firstAssessment = job.Assessments.First(); + var firstAssessment = job.Assessments.FirstOrDefault(); + if (firstAssessment == null) + { + return null; + } var allQuestions = job.Assessments.SelectMany(a => a.Questions).ToList(); if (allQuestions.Count == 0) @@ -440,4 +466,124 @@ public async Task> GetTestToJob(string id) } + public async Task> GetJobsOfCompanyAsync(string currentUserId) + { + var user = await _userManager.FindByIdAsync(currentUserId); + if (user == null || user.CompanyId == null) + { + throw new UnauthorizedAccessException("User profile not found or not associated with a company."); + } + + return await _jobRepository.GetAll() + .Include(j => j.Assessments) + .Where(j => j.CompanyId == user.CompanyId) + .Select(j => new JobViewDto + { + Id = j.Id, + CompanyId = j.CompanyId, + Title = j.Title, + ShortDescription = j.ShortDescription, + Location = j.Location, + Tags = j.Tags, + EmploymentType = j.EmploymentType, + Salary = j.Salary, + CreatedAt = j.CreatedAt, + AssessmentIds = j.Assessments.Select(a => a.Id).ToList(), + Assessments = j.Assessments.Select(a => new AssessmentViewDto + { + Id = a.Id, + Title = a.Title, + DifficultyLevel = a.DifficultyLevel + }).ToList() + }) + .ToListAsync(); + } + + public async Task ApplyForJobAsync(string jobId, string userId) + { + var jobExists = await _jobRepository.GetAll().AnyAsync(j => j.Id == jobId); + if (!jobExists) + { + throw new KeyNotFoundException("The job was not found."); + } + + var existingApplication = await _ctx.JobApplications + .FirstOrDefaultAsync(ja => ja.JobId == jobId && ja.UserId == userId); + + if (existingApplication != null) + { + throw new InvalidOperationException("You have already applied for this job."); + } + + var jobApplication = new JobApplication + { + Id = Guid.NewGuid().ToString(), + JobId = jobId, + UserId = userId, + TestId = null, + Status = JobApplicationStatus.Submitted, + AppliedAt = DateTime.UtcNow + }; + + _ctx.JobApplications.Add(jobApplication); + await _ctx.SaveChangesAsync(); + + return jobApplication.Id; + } + + public async Task AcceptCandidateAsync(string userId, string jobId) + { + var jobApplication = await _ctx.JobApplications + .FirstOrDefaultAsync(ja => ja.UserId == userId && ja.JobId == jobId); + if (jobApplication == null) + { + throw new KeyNotFoundException("The job application was not found."); + } + jobApplication.Status = JobApplicationStatus.Accepted; + jobApplication.IsRead = false; + await _ctx.SaveChangesAsync(); + } + + public async Task RejectCandidateAsync(string userId, string jobId) + { + var jobApplication = await _ctx.JobApplications + .FirstOrDefaultAsync(ja => ja.UserId == userId && ja.JobId == jobId); + if (jobApplication == null) + { + throw new KeyNotFoundException("The job application was not found."); + } + jobApplication.Status = JobApplicationStatus.Rejected; + jobApplication.IsRead = false; + await _ctx.SaveChangesAsync(); + } + + public async Task> GetNotificationsAsync(string userId) + { + var notifications = await _ctx.JobApplications + .Where(x => x.UserId == userId && !x.IsRead && + (x.Status == JobApplicationStatus.Accepted || x.Status == JobApplicationStatus.Rejected)) + .Select(x => new JobNotificationDto + { + Id = x.Id, + JobTitle = x.Job.Title, + Status = x.Status + }) + .ToListAsync(); + + return notifications; + } + + public async Task MarkNotificationAsReadAsync(string applicationId, string userId) + { + var application = await _ctx.JobApplications + .FirstOrDefaultAsync(ja => ja.Id == applicationId && ja.UserId == userId); + + if (application == null) + { + throw new KeyNotFoundException("The job application/notification was not found or does not belong to you."); + } + + application.IsRead = true; + await _ctx.SaveChangesAsync(); + } } \ No newline at end of file diff --git a/backend/SkillProof/SkillProof.Logic/Questions/QuestionBankService.cs b/backend/SkillProof/SkillProof.Logic/Questions/QuestionBankService.cs index 3bbff6d..1e0c920 100644 --- a/backend/SkillProof/SkillProof.Logic/Questions/QuestionBankService.cs +++ b/backend/SkillProof/SkillProof.Logic/Questions/QuestionBankService.cs @@ -11,6 +11,7 @@ namespace SkillProof.Logic.Questions public class QuestionBankService : IQuestionBankService { private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web); + private const string DefaultLanguage = "General"; private readonly SkillProofDbContext _dbContext; @@ -30,7 +31,7 @@ await strategy.ExecuteAsync(async () => var entity = new QuestionEntity { Type = request.Type, - Language = request.Language, + Language = NormalizeLanguage(request.Language), Difficulty = request.Difficulty, Title = request.Title, QuestionText = request.QuestionText, @@ -38,7 +39,7 @@ await strategy.ExecuteAsync(async () => CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow, IsActive = true, - Tags = request.Tags ?? new List() + Tags = NormalizeTags(request.Tags) }; await using var transaction = await _dbContext.Database.BeginTransactionAsync(cancellationToken); @@ -103,8 +104,9 @@ public async Task> GetAllAsync(QuestionListFi ValidatePayload(entity.Type, request.MultipleChoice, request.CodeCompletion, request.FillInTheBlank, request.TrueFalse); - entity.Language = request.Language; + entity.Language = NormalizeLanguage(request.Language); entity.Difficulty = request.Difficulty; + entity.Tags = NormalizeTags(request.Tags); entity.Title = request.Title; entity.QuestionText = request.QuestionText; entity.IsActive = request.IsActive; @@ -269,6 +271,7 @@ private static QuestionResponseDto MapToResponse(QuestionEntity question) Type = question.Type, Language = question.Language, Difficulty = question.Difficulty, + Tags = question.Tags ?? new List(), Title = question.Title, QuestionText = question.QuestionText, CreatedBy = question.CreatedBy, @@ -318,5 +321,35 @@ private static QuestionResponseDto MapToResponse(QuestionEntity question) return default; } } + + private static string NormalizeLanguage(string? language) + { + if (string.IsNullOrWhiteSpace(language)) + { + return DefaultLanguage; + } + + var trimmed = language.Trim(); + if (trimmed.Length > 20) + { + return trimmed[..20]; + } + + return trimmed; + } + + private static List NormalizeTags(List? tags) + { + if (tags == null || tags.Count == 0) + { + return new List(); + } + + return tags + .Where(tag => !string.IsNullOrWhiteSpace(tag)) + .Select(tag => tag.Trim()) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + } } } diff --git a/backend/SkillProof/SkillProof.Logic/Skill/SkillLogic.cs b/backend/SkillProof/SkillProof.Logic/Skill/SkillLogic.cs new file mode 100644 index 0000000..86f3057 --- /dev/null +++ b/backend/SkillProof/SkillProof.Logic/Skill/SkillLogic.cs @@ -0,0 +1,143 @@ +using Microsoft.EntityFrameworkCore; +using SkillProof.Data; +using SkillProof.Data.Repositorys; +using SkillProof.Entities.Dtos.Assesment; +using SkillProof.Entities.Dtos.Skill; +using SkillProof.Entities.Dtos.Tests; +using SkillProof.Entities.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SkillProof.Logic.Skill +{ + public class SkillLogic + { + + private readonly IRepository _skillRepository; + private readonly SkillProofDbContext _ctx; + + public SkillLogic(IRepository skillRepository, SkillProofDbContext _ctx) + { + this._skillRepository = skillRepository; + this._ctx = _ctx; + } + + public async Task> GetAllSkillsAsync() + { + return await _skillRepository.GetAll() + .Include(s => s.Assessments) + .Select(s => new ViewSkill + { + Id = s.Id, + Name = s.Name, + Assessments = s.Assessments.Select(a => new AssessmentViewDto + { + Id = a.Id, + Title = a.Title, + DifficultyLevel = a.DifficultyLevel + }).ToList() + }) + .ToListAsync(); + } + + public async Task CreateSkillAsync(SkillCreateDto model) + { + var skill = new SkillModel + { + Id = Guid.NewGuid().ToString(), + Name = model.Name + }; + await _skillRepository.Create(skill); + + return new ViewSkill { Id = skill.Id, Name = skill.Name }; + } + + public async Task GetSkillByIdAsync(string id) + { + var skill = await _skillRepository.GetOne(id); + return skill != null ? new ViewSkill { Id = skill.Id, Name = skill.Name } : null; + } + + public async Task GetCandidateTestForSkill(string skillId, string assessmentId) + { + var skill = await _skillRepository.GetAll() + .Include(s => s.Assessments) + .ThenInclude(a => a.Questions) + .ThenInclude(q => q.MultipleChoiceQuestion) + .Include(s => s.Assessments) + .ThenInclude(a => a.Questions) + .ThenInclude(q => q.CodeCompletionQuestion) + .Include(s => s.Assessments) + .ThenInclude(a => a.Questions) + .ThenInclude(q => q.FillInTheBlankQuestions) + .Include(s => s.Assessments) + .ThenInclude(a => a.Questions) + .ThenInclude(q => q.TrueFalseQuestion) + .FirstOrDefaultAsync(s => s.Id == skillId); + + if (skill == null || skill.Assessments.Count == 0) + { + return null; + } + + var targetAssessment = skill.Assessments.FirstOrDefault(a => a.Id == assessmentId); + if (targetAssessment == null) + { + return null; + } + + var targetQuestions = targetAssessment.Questions.ToList(); + if (targetQuestions.Count == 0) + { + return null; + } + + return new CandidateAssessmentDto + { + Id = targetAssessment.Id, + Title = targetAssessment.Title, + DifficultyLevel = targetAssessment.DifficultyLevel, + Questions = targetQuestions.Select(q => new CandidateQuestionDto + { + Id = q.Id, + Type = q.Type, + Title = q.Title, + QuestionText = q.QuestionText, + Language = q.Language, + Difficulty = q.Difficulty, + + MultipleChoice = q.MultipleChoiceQuestion == null ? null : new CandidateMultipleChoicePayloadDto + { + Options = string.IsNullOrWhiteSpace(q.MultipleChoiceQuestion.Options) + ? new List() + : System.Text.Json.JsonSerializer.Deserialize>(q.MultipleChoiceQuestion.Options) ?? new List(), + AllowMultipleSelection = q.MultipleChoiceQuestion.AllowMultipleSelection + }, + + CodeCompletion = q.CodeCompletionQuestion == null ? null : new CandidateCodeCompletionPayloadDto + { + CodeSnippet = q.CodeCompletionQuestion.CodeSnippet + }, + + FillInTheBlank = q.FillInTheBlankQuestions == null ? null : new CandidateFillInTheBlankPayloadDto(), + + TrueFalse = q.TrueFalseQuestion == null ? null : new CandidateTrueFalsePayloadDto() + }).ToList() + }; + } + + public async Task DeleteSkill(string id) + { + var skill = await _skillRepository.GetOne(id); + if (skill == null) + { + throw new Exception("Skill not found"); + } + await _skillRepository.DeleteById(skill.Id); + } + } +} + diff --git a/backend/SkillProof/SkillProof.Logic/SkillProof.Logic.csproj b/backend/SkillProof/SkillProof.Logic/SkillProof.Logic.csproj index 4165f6f..04cc734 100644 --- a/backend/SkillProof/SkillProof.Logic/SkillProof.Logic.csproj +++ b/backend/SkillProof/SkillProof.Logic/SkillProof.Logic.csproj @@ -8,6 +8,7 @@ + diff --git a/backend/SkillProof/SkillProof.Logic/Tests/ITestLogic.cs b/backend/SkillProof/SkillProof.Logic/Tests/ITestLogic.cs index 9c07e80..b952c05 100644 --- a/backend/SkillProof/SkillProof.Logic/Tests/ITestLogic.cs +++ b/backend/SkillProof/SkillProof.Logic/Tests/ITestLogic.cs @@ -1,8 +1,11 @@ using SkillProof.Entities.Dtos.Tests; - namespace SkillProof.Logic.Tests; public interface ITestLogic { Task SubmitTestAsync(TestSubmitDto dto, string userId); + Task SubmitTestSkillAsync(TestSubmitSkillDto dto, string userId); + Task> GetUserTestQuestionsAsync(string jobId, string userId); + Task ManualFeedbackAsync(string? feedback, double score, string testAnswerId); + Task> GetTestUsersAsync(string jobId); } diff --git a/backend/SkillProof/SkillProof.Logic/Tests/TestLogic.cs b/backend/SkillProof/SkillProof.Logic/Tests/TestLogic.cs index a9b9871..ddfe1d4 100644 --- a/backend/SkillProof/SkillProof.Logic/Tests/TestLogic.cs +++ b/backend/SkillProof/SkillProof.Logic/Tests/TestLogic.cs @@ -8,6 +8,8 @@ using QuestionEntity = SkillProof.Entities.Models.Questions; using TestEntity = SkillProof.Entities.Models.Tests; using TestAnswerEntity = SkillProof.Entities.Models.TestAnswers; +using SkillProof.Logic.Gemini; +using SkillProof.Entities.Models.Gemini; namespace SkillProof.Logic.Tests; @@ -18,11 +20,15 @@ public class TestLogic : ITestLogic private readonly IRepository _jobRepository; private readonly SkillProofDbContext _ctx; + private readonly IGeminiService _geminiService; + private readonly IRepository _skillRepository; - public TestLogic(IRepository jobRepository, SkillProofDbContext ctx) + public TestLogic(IRepository jobRepository, SkillProofDbContext ctx, IGeminiService geminiService, IRepository skillRepository) { _jobRepository = jobRepository; _ctx = ctx; + _geminiService = geminiService; + _skillRepository = skillRepository; } public async Task SubmitTestAsync(TestSubmitDto dto, string userId) @@ -66,6 +72,8 @@ public async Task SubmitTestAsync(TestSubmitDto dto, string userI .Include(j => j.Assessments) .ThenInclude(a => a.Questions) .ThenInclude(q => q.TrueFalseQuestion) + .Include(j => j.Assessments) + .ThenInclude(t => t.TestAttempts) .FirstOrDefaultAsync(j => j.Id == dto.JobId); if (job == null) @@ -109,15 +117,16 @@ public async Task SubmitTestAsync(TestSubmitDto dto, string userI Passed = false, TestAnswers = new List() }; + job.Assessments.First().TestAttempts.Add(test); var questionResults = new List(); - var totalScore = 0; + double totalScore = 0; foreach (var question in allQuestions) { answersByQuestionId.TryGetValue(question.Id, out var submitted); - var scored = ScoreQuestion(question, submitted); + var scored = ScoreQuestion(question, submitted, _geminiService); var answerEntity = new TestAnswerEntity { @@ -126,6 +135,8 @@ public async Task SubmitTestAsync(TestSubmitDto dto, string userI TestId = test.Id, FreeTextResponse = scored.FreeTextResponse, IsCorrect = scored.IsCorrect, + Score = scored.Points, + Inspected = false, AiFeedback = scored.AiFeedback }; @@ -171,7 +182,7 @@ public async Task SubmitTestAsync(TestSubmitDto dto, string userI existingApplication.Status = JobApplicationStatus.TestCompleted; jobApplication = existingApplication; } - + _ctx.Tests.Add(test); await _ctx.SaveChangesAsync(); @@ -187,16 +198,144 @@ public async Task SubmitTestAsync(TestSubmitDto dto, string userI }; } - private sealed record ScoredAnswer(int Points, bool IsCorrect, string FreeTextResponse, string AiFeedback); + public async Task SubmitTestSkillAsync(TestSubmitSkillDto dto, string userId) + { + if (string.IsNullOrWhiteSpace(userId)) + throw new UnauthorizedAccessException("A user identity is required to submit a test."); + + if (dto == null || string.IsNullOrWhiteSpace(dto.SkillId)) + throw new ArgumentException("Skill id is required."); - private static ScoredAnswer ScoreQuestion(QuestionEntity question, TestAnswerSubmitDto? submitted) + if (string.IsNullOrWhiteSpace(dto.AssessmentId)) + throw new ArgumentException("Assessment id is required."); + + if (dto.Answers == null) + throw new ArgumentException("Answers must be provided (may be empty per question, but the list itself is required)."); + + var duplicateIds = dto.Answers + .GroupBy(a => a.QuestionId) + .Where(g => g.Count() > 1) + .Select(g => g.Key) + .ToList(); + + if (duplicateIds.Count > 0) + throw new ArgumentException($"Duplicate answers provided for question(s): {string.Join(", ", duplicateIds)}."); + + var skill = await _skillRepository.GetAll() + .Include(s => s.Assessments) + .ThenInclude(a => a.Questions) + .ThenInclude(q => q.MultipleChoiceQuestion) + .Include(s => s.Assessments) + .ThenInclude(a => a.Questions) + .ThenInclude(q => q.CodeCompletionQuestion) + .Include(s => s.Assessments) + .ThenInclude(a => a.Questions) + .ThenInclude(q => q.FillInTheBlankQuestions) + .Include(s => s.Assessments) + .ThenInclude(a => a.Questions) + .ThenInclude(q => q.TrueFalseQuestion) + .Include(s => s.Assessments) + .ThenInclude(t => t.TestAttempts) + .FirstOrDefaultAsync(s => s.Id == dto.SkillId); + + if (skill == null) + throw new KeyNotFoundException($"Skill '{dto.SkillId}' not found."); + + var targetAssessment = skill.Assessments.FirstOrDefault(a => a.Id == dto.AssessmentId); + if (targetAssessment == null) + throw new InvalidOperationException("The requested assessment was not found on this skill."); + + var targetQuestions = targetAssessment.Questions.ToList(); + if (targetQuestions.Count == 0) + throw new ArgumentException("The specified assessment has no questions."); + + var questionIdSet = targetQuestions.Select(q => q.Id).ToHashSet(); + var foreignIds = dto.Answers + .Where(a => !questionIdSet.Contains(a.QuestionId)) + .Select(a => a.QuestionId) + .ToList(); + + if (foreignIds.Count > 0) + throw new ArgumentException($"Answer(s) provided for question(s) that do not belong to this specific assessment: {string.Join(", ", foreignIds)}."); + + var answersByQuestionId = dto.Answers.ToDictionary(a => a.QuestionId, a => a); + + var test = new TestEntity + { + Id = Guid.NewGuid().ToString(), + UserId = userId, + DifficultyLevel = targetAssessment.DifficultyLevel, + CompletedAt = DateTime.UtcNow, + Score = 0, + Passed = false, + TestAnswers = new List() + }; + + targetAssessment.TestAttempts.Add(test); + + var questionResults = new List(); + double totalScore = 0; + + foreach (var question in targetQuestions) + { + answersByQuestionId.TryGetValue(question.Id, out var submitted); + var scored = ScoreQuestion(question, submitted, _geminiService); + + var answerEntity = new TestAnswerEntity + { + Id = Guid.NewGuid().ToString(), + QuestionId = question.Id, + TestId = test.Id, + FreeTextResponse = scored.FreeTextResponse, + IsCorrect = scored.IsCorrect, + Score = scored.Points, + Inspected = false, + AiFeedback = scored.AiFeedback + }; + + test.TestAnswers.Add(answerEntity); + totalScore += scored.Points; + + questionResults.Add(new QuestionResultDto + { + QuestionId = question.Id, + QuestionTitle = question.Title, + Type = question.Type, + IsCorrect = scored.IsCorrect, + PointsAwarded = scored.Points, + MaxPoints = 1, + UserResponse = scored.FreeTextResponse, + AiFeedback = scored.AiFeedback + }); + } + + test.Score = totalScore; + test.Passed = totalScore * 2 >= targetQuestions.Count; + + _ctx.Tests.Add(test); + await _ctx.SaveChangesAsync(); + + return new TestResultDto + { + TestId = test.Id, + Score = test.Score, + MaxScore = targetQuestions.Count, + Passed = test.Passed, + DifficultyLevel = test.DifficultyLevel, + QuestionResults = questionResults + }; + } + + private sealed record ScoredAnswer(double Points, bool IsCorrect, string FreeTextResponse, string AiFeedback); + + private static ScoredAnswer ScoreQuestion(QuestionEntity question, TestAnswerSubmitDto? submitted, IGeminiService geminiService) { return question.Type switch { QuestionType.MultipleChoice => ScoreMultipleChoice(question, submitted), QuestionType.TrueFalse => ScoreTrueFalse(question, submitted), QuestionType.CodeCompletion => ScoreCodeCompletion(question, submitted), - QuestionType.FillInTheBlank => ScoreFillInTheBlank(submitted), + QuestionType.FillInTheBlank => ScoreFillInTheBlank(question, submitted, geminiService), _ => new ScoredAnswer(0, false, string.Empty, AiFeedbackNotApplicable) }; } @@ -253,9 +392,141 @@ private static ScoredAnswer ScoreCodeCompletion(QuestionEntity question, TestAns return new ScoredAnswer(isCorrect ? 1 : 0, isCorrect, text, AiFeedbackNotApplicable); } - private static ScoredAnswer ScoreFillInTheBlank(TestAnswerSubmitDto? submitted) + private static ScoredAnswer ScoreFillInTheBlank(QuestionEntity question, TestAnswerSubmitDto? submitted, IGeminiService geminiService) { var text = submitted?.TextAnswer ?? string.Empty; - return new ScoredAnswer(1, true, text, AiFeedbackPending); + GradingRequest request = new GradingRequest + { + Question = question.QuestionText, + StudentAnswer = text, + AnswerToQuestion = question.FillInTheBlankQuestions?.Answer ?? string.Empty + }; + double score = geminiService.EvaluateAnswerAsync(request).GetAwaiter().GetResult(); + + bool isCorrect = score >= 0.5; + return new ScoredAnswer(score, isCorrect, text, AiFeedbackPending); + } + + public async Task> GetUserTestQuestionsAsync(string jobId, string userId) + { + if (jobId == null || string.IsNullOrWhiteSpace(jobId)) + { + throw new ArgumentException("Job id is required."); + } + var job = await _jobRepository.GetAll() + .Include(j => j.JobApplications) + .Include(j => j.Assessments) + .ThenInclude(Assessments => Assessments.TestAttempts) + .ThenInclude(TestAttempts => TestAttempts.TestAnswers) + .ThenInclude(TestAnswers => TestAnswers.Question) + .FirstOrDefaultAsync(j => j.Id == jobId); + + if (job == null) + { + throw new KeyNotFoundException($"Job '{jobId}' not found."); + } + + if (job.Assessments.Count == 0) + { + return new List(); + } + + var attempt = job.Assessments.SelectMany(a => a.TestAttempts).FirstOrDefault(a => a.UserId == userId); + + + + if (attempt == null) + { + return new List(); + } + var jobApplication = job.JobApplications.FirstOrDefault(ja => ja.UserId == userId); + if (jobApplication != null && jobApplication.Status != JobApplicationStatus.Accepted && jobApplication.Status != JobApplicationStatus.Rejected) + { + jobApplication.Status = JobApplicationStatus.UnderReview; + _ctx.JobApplications.Update(jobApplication); + await _ctx.SaveChangesAsync(); + } + var reviews = attempt.TestAnswers.Select(testAnswers => new UserTestReviewDto + { + QuestionId = testAnswers.QuestionId, + TestAnswerId = testAnswers.Id, + QuestionText = testAnswers.Question.QuestionText, + UserResponse = testAnswers.FreeTextResponse, + Score = testAnswers.Score, + Inspected = testAnswers.Inspected, + UserId = userId, + QuestionType = testAnswers.Question.Type + }).ToList(); + return reviews; + } + + public async Task ManualFeedbackAsync(string? feedback, double score, string testAnswerId) + { + if (testAnswerId == null || string.IsNullOrWhiteSpace(testAnswerId)) + { + throw new ArgumentException("Test answer id is required."); + } + + var testAnwser = await _ctx.TestAnswers + .Include(a => a.Question) + .FirstOrDefaultAsync(a => a.Id == testAnswerId); + + if (testAnwser == null) + { + throw new KeyNotFoundException($"Test answer '{testAnswerId}' not found."); + } + + if (!(testAnwser.Question.Type == QuestionType.FillInTheBlank)) + { + throw new ArgumentException("Manual feedback can only be provided for fill-in-the-blank questions."); + } + + testAnwser.Score = score; + testAnwser.IsCorrect = score >= 0.5; + testAnwser.Inspected = true; + if (!string.IsNullOrWhiteSpace(feedback)) + { + testAnwser.ManualFeedback = feedback; + } + + var result = new FeedbackResponseDto + { + TestAnswerId = testAnwser.Id, + QuestionText = testAnwser.Question.QuestionText, + UserResponse = testAnwser.FreeTextResponse, + Score = testAnwser.Score, + Inspected = testAnwser.Inspected + }; + _ctx.TestAnswers.Update(testAnwser); + await _ctx.SaveChangesAsync(); + return result; + } + + public async Task> GetTestUsersAsync(string jobId) + { + if (jobId == null || string.IsNullOrWhiteSpace(jobId)) + { + throw new ArgumentException("Job id is required."); + } + var job = await _jobRepository.GetAll() + .Include(j => j.JobApplications) + .FirstOrDefaultAsync(j => j.Id == jobId); + + if (job == null) + { + throw new KeyNotFoundException($"Job '{jobId}' not found."); + } + + List JobApplicationStatusDto = job.JobApplications + .Select(a => new JobApplicationStatusDto + { + UserId = a.UserId, + jobApplicationStatus = a.Status + }) + .Distinct() + .ToList(); + + return JobApplicationStatusDto; } + } diff --git a/backend/SkillProof/SkillProof.Logic/User/IUserLogic.cs b/backend/SkillProof/SkillProof.Logic/User/IUserLogic.cs index 9b0b615..6d637f9 100644 --- a/backend/SkillProof/SkillProof.Logic/User/IUserLogic.cs +++ b/backend/SkillProof/SkillProof.Logic/User/IUserLogic.cs @@ -1,3 +1,4 @@ +using SkillProof.Entities.Dtos.Tests; using SkillProof.Entities.Dtos.Users; namespace SkillProof.Logic.User; @@ -13,4 +14,9 @@ public interface IUserLogic Task LoginAsync(LoginUser dto); Task GrantAdminRoleAsync(string userId); Task RevokeRoleAsync(string userId); + Task> GetUserTestsAsync(string userId); + Task UpdateSkillsToUser(string userId, string[] skillIds); + Task ToggleSavedJobAsync(string userId, string jobId); + Task ApplyToJobAsync(string userId, string jobId); + Task DeleteSkillFromUser(string userId, string skillId); } \ No newline at end of file diff --git a/backend/SkillProof/SkillProof.Logic/User/UserLogic.cs b/backend/SkillProof/SkillProof.Logic/User/UserLogic.cs index c58cf89..3f60eca 100644 --- a/backend/SkillProof/SkillProof.Logic/User/UserLogic.cs +++ b/backend/SkillProof/SkillProof.Logic/User/UserLogic.cs @@ -1,16 +1,25 @@ -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using System.Text; -using System.Text.RegularExpressions; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; +using SkillProof.Data; using SkillProof.Data.Repositorys; +using SkillProof.Entities.Dtos.Assesment; +using SkillProof.Entities.Dtos.Skill; +using SkillProof.Entities.Dtos.Tests; using SkillProof.Entities.Dtos.Users; +using SkillProof.Entities.Enums; using SkillProof.Entities.Helper; using SkillProof.Entities.Models; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using System.Text.RegularExpressions; +using SkillProof.Entities.Dtos.Education; +using SkillProof.Entities.Dtos.Experience; namespace SkillProof.Logic.User { @@ -20,21 +29,33 @@ public class UserLogic : IUserLogic private readonly UserManager _userManager; private readonly RoleManager _roleManager; private readonly IRepository _companyRepository; + private readonly IRepository _jobRepository; + private readonly IRepository _applicationRepository; + private readonly IRepository _skillRepository; private readonly IWebHostEnvironment _env; private readonly JwtSettings _jwtSettings; + private readonly SkillProofDbContext _ctx; public UserLogic( UserManager userManager, RoleManager roleManager, IRepository companyRepository, + IRepository jobRepository, + IRepository applicationRepository, IWebHostEnvironment env, - IOptions jwtSettings) + IOptions jwtSettings, + SkillProofDbContext ctx, + IRepository skillRepository) { _userManager = userManager; _roleManager = roleManager; _companyRepository = companyRepository; + _jobRepository = jobRepository; + _applicationRepository = applicationRepository; _env = env; _jwtSettings = jwtSettings.Value; + _ctx = ctx; + this._skillRepository = skillRepository; } public async Task RegisterUserAsync(RegisterUser dto) @@ -128,42 +149,125 @@ public async Task RegisterEmployerAsync(RegisterEmployer dto) public async Task> GetAllUsersAsync() { - var users = await _userManager.Users.ToListAsync(); - var result = new List(); + var users = await _userManager.Users + .Include(u => u.Skills) + .ToListAsync(); - foreach (var user in users) - { - result.Add(new ViewUser + var result = new List(); + + foreach (var user in users) { - Id = user.Id, - Email = user.Email, - FullName = user.FirstName + " " + user.LastName, - Image = Convert.ToBase64String(user.ProfilePicture), - Headline = user.Headline, - Bio = user.Bio - }); - } + result.Add(new ViewUser + { + Id = user.Id, + Email = user.Email, + FullName = user.FirstName + " " + user.LastName, + + Image = user.ProfilePicture != null + ? Convert.ToBase64String(user.ProfilePicture) + : string.Empty, + + Headline = user.Headline, + Bio = user.Bio, + + Skills = user.Skills?.Select(s => new ViewSkill + { + Id = s.Id, + Name = s.Name + }).ToList() ?? new List() + }); + } return result; } public async Task GetUserByIdAsync(string id) { - var user = await _userManager.FindByIdAsync(id); + var user = await _userManager.Users + .Include(u => u.SavedJobs) + .Include(u => u.JobApplications) + .Include(u => u.Tests) + .Include(u => u.Educations) + .Include(u => u.UserExperiences) + .Include(u => u.Skills) + .ThenInclude(s => s.Assessments) + .ThenInclude(a => a.TestAttempts) + .FirstOrDefaultAsync(u => u.Id == id); + if (user == null) { throw new KeyNotFoundException("User not found."); } + var badges = new List(); + + var passedTests = user.Tests?.Where(t => t.Passed).ToList() ?? new List(); + + if (user.Skills != null) + { + foreach (var skill in user.Skills) + { + foreach (var assessment in skill.Assessments) + { + var passedTest = assessment.TestAttempts + .Where(t => t.UserId == user.Id && t.Passed) + .OrderByDescending(t => t.CompletedAt) + .FirstOrDefault(); + + if (passedTest != null) + { + badges.Add(new BadgeDto + { + SourceName = skill.Name, + DifficultyLevel = assessment.DifficultyLevel, + IssuedAt = passedTest.CompletedAt + }); + } + } + } + } + return new ViewUser { Id = user.Id, FullName = $"{user.FirstName} {user.LastName}", Email = user.Email, - Image = Convert.ToBase64String(user.ProfilePicture), + Image = user.ProfilePicture != null ? Convert.ToBase64String(user.ProfilePicture) : null, Bio = user.Bio, Headline = user.Headline, - CompanyId = user.CompanyId + CompanyId = user.CompanyId, + SavedJobIds = user.SavedJobs?.Select(j => j.Id).ToList() ?? new List(), + + AppliedJobIds = user.JobApplications?.Select(ja => ja.JobId).ToList() ?? new List(), + + Skills = user.Skills?.Select(s => new ViewSkill + { + Id = s.Id, + Name = s.Name, + Assessments = s.Assessments?.Select(a => new AssessmentViewDto + { + Id = a.Id, + Title = a.Title, + DifficultyLevel = a.DifficultyLevel + }).ToList() ?? new List() + }).ToList() ?? new List(), + + Badges = badges, + Educations = user.Educations?.Select(e => new EducationViewDto + { + School = e.School, + Degree = e.Degree, + FieldOfStudy = e.FieldOfStudy, + StartDate = e.StartDate, + EndDate = e.EndDate + }).ToList() ?? new List(), + Experiences = user.UserExperiences?.Select(ex => new ExperienceViewDto + { + JobTitle = ex.JobTitle, + CompanyName = ex.CompanyName, + StartDate = ex.StartDate, + EndDate = ex.EndDate + }).ToList() ?? new List() }; } @@ -190,6 +294,51 @@ public async Task UpdateUserAsync(string id, UpdateUser dto) await _userManager.UpdateAsync(currentUser); } + public async Task ToggleSavedJobAsync(string userId, string jobId) + { + var user = await _userManager.Users + .Include(u => u.SavedJobs) + .Include(u => u.JobApplications) + .FirstOrDefaultAsync(u => u.Id == userId); + + if (user == null) + { + throw new KeyNotFoundException($"User not found. Provided ID: '{userId}'"); + } + + var job = await _jobRepository.GetOne(jobId); + + if (job == null) + { + throw new KeyNotFoundException("Job not found."); + } + + var existingJob = user.SavedJobs.FirstOrDefault(j => j.Id == jobId); + + if (existingJob != null) + { + user.SavedJobs.Remove(existingJob); + } + else + { + user.SavedJobs.Add(job); + } + + await _userManager.UpdateAsync(user); + + return new ViewUser + { + Id = user.Id, + FullName = user.FirstName + " " + user.LastName, + Email = user.Email, + Headline = user.Headline, + Bio = user.Bio, + CompanyId = user.CompanyId, + SavedJobIds = user.SavedJobs.Select(j => j.Id).ToList(), + AppliedJobIds = user.JobApplications?.Select(ja => ja.JobId).ToList() ?? new List(), + }; + } + public async Task DeleteUserAsync(string id) { var user = await _userManager.FindByIdAsync(id); @@ -248,6 +397,11 @@ public async Task GrantAdminRoleAsync(string userId) if (user == null) throw new KeyNotFoundException("User not found."); + if (!await _roleManager.RoleExistsAsync("Admin")) + { + await _roleManager.CreateAsync(new IdentityRole("Admin")); + } + var roles = await _userManager.GetRolesAsync(user); if (roles.Contains("Admin")) { @@ -297,5 +451,106 @@ private JwtSecurityToken GenerateAccessToken(IEnumerable? claims, int exp signingCredentials: new SigningCredentials(signinKey, SecurityAlgorithms.HmacSha256) ); } + + public async Task> GetUserTestsAsync(string userId) + { + var user = await _userManager.FindByIdAsync(userId); + + if (user == null) + { + throw new KeyNotFoundException("User not found."); + } + var result = new List(); + + foreach (var test in user.Tests) + { + result.Add(new UserTestsDto + { + DifficultyLevel = test.DifficultyLevel, + Passed = test.Passed + }); + } + return result; + } + + public async Task UpdateSkillsToUser(string id, string[] skillsId) + { + var user = await _userManager.Users + .Include(u => u.Skills) + .FirstOrDefaultAsync(u => u.Id == id); + + if (user == null) + { + throw new KeyNotFoundException("User not found"); + } + + user.Skills ??= new List(); + + if (skillsId != null) + { + foreach (var skillId in skillsId) + { + var existingSkill = await _skillRepository.GetOne(skillId); + if (existingSkill == null) + { + continue; + } + + if (user.Skills.Any(s => s.Id == skillId)) + { + continue; + } + + user.Skills.Add(existingSkill); + } + } + + var result = await _userManager.UpdateAsync(user); + + await _ctx.SaveChangesAsync(); + } + + public async Task ApplyToJobAsync(string userId, string jobId) + { + var allApplications = _applicationRepository.GetAll(); + var alreadyApplied = await allApplications + .AnyAsync(ja => ja.JobId == jobId && ja.UserId == userId); + + if (alreadyApplied) + { + throw new InvalidOperationException("You have already applied for this job."); + } + + var application = new JobApplication + { + Id = Guid.NewGuid().ToString(), + UserId = userId, + JobId = jobId, + AppliedAt = DateTime.UtcNow, + Status = JobApplicationStatus.Submitted, + TestId = null + }; + + await _applicationRepository.Create(application); + } + + public async Task DeleteSkillFromUser(string userId, string skillId) + { + var user = await _userManager.Users + .Include(u => u.Skills) + .FirstOrDefaultAsync(u => u.Id == userId); + if (user == null) + { + throw new KeyNotFoundException("User not found"); + } + var skillToRemove = user.Skills.FirstOrDefault(s => s.Id == skillId); + if (skillToRemove == null) + { + throw new KeyNotFoundException("Skill not found in user's skills"); + } + user.Skills.Remove(skillToRemove); + var result = await _userManager.UpdateAsync(user); + await _ctx.SaveChangesAsync(); + } } } \ No newline at end of file diff --git a/web/angular.json b/web/angular.json index c1146a3..95e649d 100644 --- a/web/angular.json +++ b/web/angular.json @@ -54,8 +54,8 @@ }, { "type": "anyComponentStyle", - "maximumWarning": "4kB", - "maximumError": "8kB" + "maximumWarning": "12kB", + "maximumError": "16kB" } ], "outputHashing": "all" diff --git a/web/public/Assets/Arrow.svg b/web/public/Assets/Arrow.svg new file mode 100644 index 0000000..375cbe6 --- /dev/null +++ b/web/public/Assets/Arrow.svg @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/web/public/Assets/Dropdown.svg b/web/public/Assets/Dropdown.svg new file mode 100644 index 0000000..ff7705e --- /dev/null +++ b/web/public/Assets/Dropdown.svg @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/web/public/Assets/Junior.svg b/web/public/Assets/Junior.svg new file mode 100644 index 0000000..8d0b87c --- /dev/null +++ b/web/public/Assets/Junior.svg @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file diff --git a/web/public/Assets/Medior.svg b/web/public/Assets/Medior.svg new file mode 100644 index 0000000..3f19aa2 --- /dev/null +++ b/web/public/Assets/Medior.svg @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file diff --git a/web/public/Assets/Senior.svg b/web/public/Assets/Senior.svg new file mode 100644 index 0000000..a4e3961 --- /dev/null +++ b/web/public/Assets/Senior.svg @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file diff --git a/web/public/Assets/Unknown.svg b/web/public/Assets/Unknown.svg new file mode 100644 index 0000000..7e47817 --- /dev/null +++ b/web/public/Assets/Unknown.svg @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/web/public/Assets/profile.svg b/web/public/Assets/profile.svg new file mode 100644 index 0000000..2fb16e6 --- /dev/null +++ b/web/public/Assets/profile.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/app/Models/Dtos/Assesment/AddAssessmentToSkillDto.ts b/web/src/app/Models/Dtos/Assesment/AddAssessmentToSkillDto.ts new file mode 100644 index 0000000..0c27150 --- /dev/null +++ b/web/src/app/Models/Dtos/Assesment/AddAssessmentToSkillDto.ts @@ -0,0 +1,4 @@ +export class AddAssessmentToSkillDto { + skillId: string = ''; + assessmentId: string = ''; +} \ No newline at end of file diff --git a/web/src/app/Models/Dtos/Education/EducationCreateDto.ts b/web/src/app/Models/Dtos/Education/EducationCreateDto.ts new file mode 100644 index 0000000..f2d9808 --- /dev/null +++ b/web/src/app/Models/Dtos/Education/EducationCreateDto.ts @@ -0,0 +1,8 @@ +export class EducationCreateDto { + school: string = ''; + degree: string = ''; + fieldOfStudy: string = ''; + startDate: string = ''; + endDate?: string | null = null; + description: string = ''; +} \ No newline at end of file diff --git a/web/src/app/Models/Dtos/Education/EducationViewDto.ts b/web/src/app/Models/Dtos/Education/EducationViewDto.ts new file mode 100644 index 0000000..37ff86a --- /dev/null +++ b/web/src/app/Models/Dtos/Education/EducationViewDto.ts @@ -0,0 +1,10 @@ +export class EducationViewDto { + id: string = ''; + school: string = ''; + degree: string = ''; + fieldOfStudy: string = ''; + startDate: string = ''; + endDate?: string | null = null; + isOngoing: boolean = false; + description: string = ''; +} \ No newline at end of file diff --git a/web/src/app/Models/Dtos/Experience/ExperienceCreateDto.ts b/web/src/app/Models/Dtos/Experience/ExperienceCreateDto.ts new file mode 100644 index 0000000..721cde9 --- /dev/null +++ b/web/src/app/Models/Dtos/Experience/ExperienceCreateDto.ts @@ -0,0 +1,6 @@ +export class ExperienceCreateDto { + jobTitle: string = ''; + companyName: string = ''; + startDate: string = ''; + endDate: string = ''; +} \ No newline at end of file diff --git a/web/src/app/Models/Dtos/Experience/ExperienceViewDto.ts b/web/src/app/Models/Dtos/Experience/ExperienceViewDto.ts new file mode 100644 index 0000000..68e2ce9 --- /dev/null +++ b/web/src/app/Models/Dtos/Experience/ExperienceViewDto.ts @@ -0,0 +1,7 @@ +export class ExperienceViewDto { + id: string = ''; + jobTitle: string = ''; + companyName: string = ''; + startDate: string = ''; + endDate: string = ''; +} \ No newline at end of file diff --git a/web/src/app/Models/Dtos/Job/JobCreate-dto.ts b/web/src/app/Models/Dtos/Job/JobCreate-dto.ts index 569dc83..a797d68 100644 --- a/web/src/app/Models/Dtos/Job/JobCreate-dto.ts +++ b/web/src/app/Models/Dtos/Job/JobCreate-dto.ts @@ -4,8 +4,9 @@ export class JobCreateDto{ companyId: string = "" title: string = "" description: string = "" - EmploymentType: EmploymentType | null = null + employmentType: EmploymentType | null = null + shortDescription: string = "" location: string = "" tags: string[] = [] //companyName: string = "" -} \ No newline at end of file +} diff --git a/web/src/app/Models/Dtos/Job/JobView-dto.ts b/web/src/app/Models/Dtos/Job/JobView-dto.ts index bba3e49..0730d6c 100644 --- a/web/src/app/Models/Dtos/Job/JobView-dto.ts +++ b/web/src/app/Models/Dtos/Job/JobView-dto.ts @@ -1,4 +1,4 @@ -import { EmploymentType } from "../../Enums/EmploymentType" +import { EmploymentType } from '../../Enums/EmploymentType'; import { AssessmentViewDto } from '../Assesment/AssessmentViewDto'; export class JobViewDto { @@ -6,7 +6,9 @@ export class JobViewDto { companyId: string = ''; title: string = ''; description: string = ''; - EmploymentType: EmploymentType | null = null; + employmentType: EmploymentType | null = null; + shortDescription = ''; + salary: number | null = null; location: string = ''; tags: string[] = []; createdAt: string = ''; diff --git a/web/src/app/Models/Dtos/Job/job-application-status-dto.spec.ts b/web/src/app/Models/Dtos/Job/job-application-status-dto.spec.ts new file mode 100644 index 0000000..81730ac --- /dev/null +++ b/web/src/app/Models/Dtos/Job/job-application-status-dto.spec.ts @@ -0,0 +1,7 @@ +import { JobApplicationStatusDto } from './job-application-status-dto'; + +describe('JobApplicationStatusDto', () => { + it('should create an instance', () => { + expect(new JobApplicationStatusDto()).toBeTruthy(); + }); +}); diff --git a/web/src/app/Models/Dtos/Job/job-application-status-dto.ts b/web/src/app/Models/Dtos/Job/job-application-status-dto.ts new file mode 100644 index 0000000..3bffdfd --- /dev/null +++ b/web/src/app/Models/Dtos/Job/job-application-status-dto.ts @@ -0,0 +1,5 @@ +import { JobApplicationStatus } from "../../Enums/Status"; +export class JobApplicationStatusDto { + jobApplicationStatus: JobApplicationStatus = JobApplicationStatus.Withdrawn + userId: string = "" +} diff --git a/web/src/app/Models/Dtos/Job/job.ts b/web/src/app/Models/Dtos/Job/job.ts index 2ce6b08..73d1b8e 100644 --- a/web/src/app/Models/Dtos/Job/job.ts +++ b/web/src/app/Models/Dtos/Job/job.ts @@ -1,12 +1,16 @@ -import { EmploymentType } from "../../Enums/EmploymentType" +import { EmploymentType } from '../../Enums/EmploymentType'; -export class Job{ - id: string = "" - companyId: string = "" - title: string = "" - description: string = "" - EmploymentType: EmploymentType | null = null - location: string = "" - tags: string = "" - createdAt: string = "" +export class Job { + id: string = ''; + companyId: string = ''; + title: string = ''; + description: string = ''; + employmentType: EmploymentType | null = null; + shortDescription: string = ''; + location: string = ''; + salary: number | null = null; + tags: string = ''; + createdAt: string = ''; + assessments?: any[] = []; + assessmentIds?: string[] = []; } diff --git a/web/src/app/Models/Dtos/Job/jobNotificationDto.ts b/web/src/app/Models/Dtos/Job/jobNotificationDto.ts new file mode 100644 index 0000000..1220e70 --- /dev/null +++ b/web/src/app/Models/Dtos/Job/jobNotificationDto.ts @@ -0,0 +1,7 @@ +import { JobApplicationStatus } from "../../Enums/Status"; + +export class JobNotificationDto { + id: string = ''; + jobTitle: string = ''; + status: JobApplicationStatus = JobApplicationStatus.Submitted; +} \ No newline at end of file diff --git a/web/src/app/Models/Dtos/Question/create-question-request-dto.ts b/web/src/app/Models/Dtos/Question/create-question-request-dto.ts index 56d77ac..b056ecc 100644 --- a/web/src/app/Models/Dtos/Question/create-question-request-dto.ts +++ b/web/src/app/Models/Dtos/Question/create-question-request-dto.ts @@ -1,17 +1,20 @@ import { DifficultyLevel } from '../../Enums/DifficultyLevel'; import { QuestionType } from '../../Enums/QuestionType'; -import { CodeCompletionQuestionPayloadDto, FillInTheBlankQuestionPayloadDto, MultipleChoiceQuestionPayloadDto, TrueFalseQuestionPayloadDto} from './question-type-payload-dtos'; +import { CodeCompletionQuestionPayloadDto, FillInTheBlankQuestionPayloadDto, MultipleChoiceQuestionPayloadDto, OpenEndedQuestionPayloadDto, TrueFalseQuestionPayloadDto} from './question-type-payload-dtos'; export class CreateQuestionRequestDto { type: QuestionType = QuestionType.MultipleChoice; - language = ''; + language?: string; difficulty: DifficultyLevel = DifficultyLevel.Junior; + tags: string[] = []; title = ''; questionText = ''; createdBy = ''; multipleChoice?: MultipleChoiceQuestionPayloadDto; codeCompletion?: CodeCompletionQuestionPayloadDto; + openEnded?: OpenEndedQuestionPayloadDto; + // TODO(OpenEnded-cleanup): remove legacy wire property after backend contract rename. fillInTheBlank?: FillInTheBlankQuestionPayloadDto; trueFalse?: TrueFalseQuestionPayloadDto; } diff --git a/web/src/app/Models/Dtos/Question/question-response-dto.ts b/web/src/app/Models/Dtos/Question/question-response-dto.ts index f6c35e4..f90e9f6 100644 --- a/web/src/app/Models/Dtos/Question/question-response-dto.ts +++ b/web/src/app/Models/Dtos/Question/question-response-dto.ts @@ -1,12 +1,13 @@ import { DifficultyLevel } from '../../Enums/DifficultyLevel'; import { QuestionType } from '../../Enums/QuestionType'; -import {CodeCompletionQuestionPayloadDto, FillInTheBlankQuestionPayloadDto, MultipleChoiceQuestionPayloadDto, TrueFalseQuestionPayloadDto } from './question-type-payload-dtos'; +import {CodeCompletionQuestionPayloadDto, FillInTheBlankQuestionPayloadDto, MultipleChoiceQuestionPayloadDto, OpenEndedQuestionPayloadDto, TrueFalseQuestionPayloadDto } from './question-type-payload-dtos'; export class QuestionResponseDto { id = ''; type: QuestionType = QuestionType.MultipleChoice; language = ''; difficulty: DifficultyLevel = DifficultyLevel.Junior; + tags: string[] = []; title = ''; questionText = ''; createdBy = ''; @@ -16,6 +17,8 @@ export class QuestionResponseDto { multipleChoice?: MultipleChoiceQuestionPayloadDto; codeCompletion?: CodeCompletionQuestionPayloadDto; + openEnded?: OpenEndedQuestionPayloadDto; + // TODO(OpenEnded-cleanup): remove legacy wire property after backend contract rename. fillInTheBlank?: FillInTheBlankQuestionPayloadDto; trueFalse?: TrueFalseQuestionPayloadDto; } diff --git a/web/src/app/Models/Dtos/Question/question-type-payload-dtos.ts b/web/src/app/Models/Dtos/Question/question-type-payload-dtos.ts index 0de3738..cad8929 100644 --- a/web/src/app/Models/Dtos/Question/question-type-payload-dtos.ts +++ b/web/src/app/Models/Dtos/Question/question-type-payload-dtos.ts @@ -9,11 +9,14 @@ export class CodeCompletionQuestionPayloadDto { acceptedAnswers: string[] = []; } -export class FillInTheBlankQuestionPayloadDto { +export class OpenEndedQuestionPayloadDto { answer = ''; manualFeedback?: string; } +// TODO(OpenEnded-cleanup): remove legacy alias after backend contract rename. +export class FillInTheBlankQuestionPayloadDto extends OpenEndedQuestionPayloadDto {} + export class TrueFalseQuestionPayloadDto { correctAnswer = false; explanation?: string; diff --git a/web/src/app/Models/Dtos/Question/update-question-request-dto.ts b/web/src/app/Models/Dtos/Question/update-question-request-dto.ts index be28760..73edbed 100644 --- a/web/src/app/Models/Dtos/Question/update-question-request-dto.ts +++ b/web/src/app/Models/Dtos/Question/update-question-request-dto.ts @@ -1,15 +1,18 @@ import { DifficultyLevel } from "../../Enums/DifficultyLevel"; -import { CodeCompletionQuestionPayloadDto, FillInTheBlankQuestionPayloadDto, MultipleChoiceQuestionPayloadDto, TrueFalseQuestionPayloadDto } from "./question-type-payload-dtos"; +import { CodeCompletionQuestionPayloadDto, FillInTheBlankQuestionPayloadDto, MultipleChoiceQuestionPayloadDto, OpenEndedQuestionPayloadDto, TrueFalseQuestionPayloadDto } from "./question-type-payload-dtos"; export class UpdateQuestionRequestDto { - language = ''; + language?: string; difficulty: DifficultyLevel = DifficultyLevel.Junior; + tags: string[] = []; title = ''; questionText = ''; isActive = true; multipleChoice?: MultipleChoiceQuestionPayloadDto; codeCompletion?: CodeCompletionQuestionPayloadDto; + openEnded?: OpenEndedQuestionPayloadDto; + // TODO(OpenEnded-cleanup): remove legacy wire property after backend contract rename. fillInTheBlank?: FillInTheBlankQuestionPayloadDto; trueFalse?: TrueFalseQuestionPayloadDto; } diff --git a/web/src/app/Models/Dtos/Skill/skill-create-dto.ts b/web/src/app/Models/Dtos/Skill/skill-create-dto.ts new file mode 100644 index 0000000..6948ad9 --- /dev/null +++ b/web/src/app/Models/Dtos/Skill/skill-create-dto.ts @@ -0,0 +1,6 @@ +import { AssessmentViewDto } from "../Assesment/AssessmentViewDto"; + +export class SkillCreateDto { + name: string = ''; + assesments: AssessmentViewDto[] | null = null; +} \ No newline at end of file diff --git a/web/src/app/Models/Dtos/Skill/skill-view-dto.ts b/web/src/app/Models/Dtos/Skill/skill-view-dto.ts new file mode 100644 index 0000000..3d906d2 --- /dev/null +++ b/web/src/app/Models/Dtos/Skill/skill-view-dto.ts @@ -0,0 +1,7 @@ +import { AssessmentViewDto } from "../Assesment/AssessmentViewDto"; + +export class SkillViewDto { + id: string = ''; + name: string = ''; + assessments: AssessmentViewDto[] | null = null; +} \ No newline at end of file diff --git a/web/src/app/Models/Dtos/Test/candidate-fill-in-the-blank-payload-dto.ts b/web/src/app/Models/Dtos/Test/candidate-fill-in-the-blank-payload-dto.ts index 21dd5aa..a356bfc 100644 --- a/web/src/app/Models/Dtos/Test/candidate-fill-in-the-blank-payload-dto.ts +++ b/web/src/app/Models/Dtos/Test/candidate-fill-in-the-blank-payload-dto.ts @@ -1 +1,4 @@ -export interface CandidateFillInTheBlankPayloadDto {} +import { CandidateOpenEndedPayloadDto } from './candidate-open-ended-payload-dto'; + +// TODO(OpenEnded-cleanup): remove legacy alias after backend contract rename. +export type CandidateFillInTheBlankPayloadDto = CandidateOpenEndedPayloadDto; diff --git a/web/src/app/Models/Dtos/Test/candidate-open-ended-payload-dto.ts b/web/src/app/Models/Dtos/Test/candidate-open-ended-payload-dto.ts new file mode 100644 index 0000000..e65d3f6 --- /dev/null +++ b/web/src/app/Models/Dtos/Test/candidate-open-ended-payload-dto.ts @@ -0,0 +1 @@ +export interface CandidateOpenEndedPayloadDto {} diff --git a/web/src/app/Models/Dtos/Test/candidate-question-dto.ts b/web/src/app/Models/Dtos/Test/candidate-question-dto.ts index 9ef876f..430323b 100644 --- a/web/src/app/Models/Dtos/Test/candidate-question-dto.ts +++ b/web/src/app/Models/Dtos/Test/candidate-question-dto.ts @@ -1,8 +1,8 @@ import { DifficultyLevel } from '../../Enums/DifficultyLevel'; import { QuestionType } from '../../Enums/QuestionType'; import { CandidateCodeCompletionPayloadDto } from './candidate-code-completion-payload-dto'; -import { CandidateFillInTheBlankPayloadDto } from './candidate-fill-in-the-blank-payload-dto'; import { CandidateMultipleChoicePayloadDto } from './candidate-multiple-choice-payload-dto'; +import { CandidateOpenEndedPayloadDto } from './candidate-open-ended-payload-dto'; import { CandidateTrueFalsePayloadDto } from './candidate-true-false-payload-dto'; export interface CandidateQuestionDto { @@ -15,6 +15,8 @@ export interface CandidateQuestionDto { multipleChoice?: CandidateMultipleChoicePayloadDto; codeCompletion?: CandidateCodeCompletionPayloadDto; - fillInTheBlank?: CandidateFillInTheBlankPayloadDto; + openEnded?: CandidateOpenEndedPayloadDto; + // TODO(OpenEnded-cleanup): remove legacy wire property after backend contract rename. + fillInTheBlank?: CandidateOpenEndedPayloadDto; trueFalse?: CandidateTrueFalsePayloadDto; } diff --git a/web/src/app/Models/Dtos/Test/test-submit-skill-dto.ts b/web/src/app/Models/Dtos/Test/test-submit-skill-dto.ts new file mode 100644 index 0000000..db6c271 --- /dev/null +++ b/web/src/app/Models/Dtos/Test/test-submit-skill-dto.ts @@ -0,0 +1,8 @@ +import { TestAnswerSubmitDto } from "./test-answer-submit-dto"; + +export class TestSubmitSkillDto +{ + skillId: string =''; + assessmentId: string =''; + answers: TestAnswerSubmitDto[] | null = null; +} \ No newline at end of file diff --git a/web/src/app/Models/Dtos/User/badge-dto.ts b/web/src/app/Models/Dtos/User/badge-dto.ts new file mode 100644 index 0000000..37b03ea --- /dev/null +++ b/web/src/app/Models/Dtos/User/badge-dto.ts @@ -0,0 +1,7 @@ +import { DifficultyLevel } from "../../Enums/DifficultyLevel"; + +export interface BadgeDto { + sourceName: string; + difficultyLevel: DifficultyLevel; + issuedAt: Date; +} \ No newline at end of file diff --git a/web/src/app/Models/Dtos/User/profile-view-dto.ts b/web/src/app/Models/Dtos/User/profile-view-dto.ts new file mode 100644 index 0000000..121af3b --- /dev/null +++ b/web/src/app/Models/Dtos/User/profile-view-dto.ts @@ -0,0 +1,20 @@ +import { EducationViewDto } from "../Education/EducationViewDto"; +import { SkillViewDto } from "../Skill/skill-view-dto"; +import { BadgeDto } from "./badge-dto"; +import { ExperienceViewDto } from "../Experience/ExperienceViewDto"; + +export interface ProfileViewDto { + id: string; + fullName: string; + email: string; + headline: string; + bio: string; + image: string; + companyId?: string; + savedJobIds: string[]; + skills: SkillViewDto[]; + badges: BadgeDto[]; + appliedJobIds: string[]; + education: EducationViewDto[]; + userExperience: ExperienceViewDto[]; +} diff --git a/web/src/app/Models/Dtos/User/userTests-dto.ts b/web/src/app/Models/Dtos/User/userTests-dto.ts new file mode 100644 index 0000000..e880fc4 --- /dev/null +++ b/web/src/app/Models/Dtos/User/userTests-dto.ts @@ -0,0 +1,12 @@ +import { QuestionType } from "../../Enums/QuestionType"; + +export interface UserTestsDto { + questionId: string; + testAnswerId: string; + score: number; + questionText: string; + userResponse: string; + isInspected: boolean; + userId: string; + questionType: QuestionType; +} \ No newline at end of file diff --git a/web/src/app/Models/Enums/QuestionType.ts b/web/src/app/Models/Enums/QuestionType.ts index 78b75b5..e2a3198 100644 --- a/web/src/app/Models/Enums/QuestionType.ts +++ b/web/src/app/Models/Enums/QuestionType.ts @@ -2,5 +2,7 @@ export enum QuestionType { MultipleChoice = 'MultipleChoice', CodeCompletion = 'CodeCompletion', TrueFalse = 'TrueFalse', + OpenEnded = 'FillInTheBlank', + // TODO(OpenEnded-cleanup): remove legacy alias after backend contract rename. FillInTheBlank = 'FillInTheBlank', } diff --git a/web/src/app/Models/Enums/Status.ts b/web/src/app/Models/Enums/Status.ts new file mode 100644 index 0000000..b689d5e --- /dev/null +++ b/web/src/app/Models/Enums/Status.ts @@ -0,0 +1,9 @@ +export enum JobApplicationStatus { + Submitted, + UnderReview, + TestAssigned, + TestCompleted, + Accepted, + Rejected, + Withdrawn +} \ No newline at end of file diff --git a/web/src/app/Models/User/profile-view-dto.ts b/web/src/app/Models/User/profile-view-dto.ts deleted file mode 100644 index 910703f..0000000 --- a/web/src/app/Models/User/profile-view-dto.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface ProfileViewDto { - id: string; - email: string; - fullName: string; - image: string; - headline: string; - bio: string; - companyId: string; -} diff --git a/web/src/app/app-module.ts b/web/src/app/app-module.ts index a8e564e..6310e01 100644 --- a/web/src/app/app-module.ts +++ b/web/src/app/app-module.ts @@ -23,8 +23,16 @@ import { TestTake } from './components/test-take/test-take'; import { QuestionTrueFalse } from './components/question-true-false/question-true-false'; import { QuestionMultipleChoice } from './components/question-multiple-choice/question-multiple-choice'; import { QuestionCodeCompletion } from './components/question-code-completion/question-code-completion'; -import { QuestionFillInTheBlank } from './components/question-fill-in-the-blank/question-fill-in-the-blank'; - +import { CompanyHome } from './components/company-home/company-home'; +import { QuestionOpenEnded } from './components/question-open-ended/question-open-ended'; +import { Router, RouterModule } from '@angular/router'; +import { AsyncPipe, CommonModule } from '@angular/common'; +import { MyJobs } from './components/my-jobs/my-jobs'; +import { ReviewUser } from './components/review-user/review-user'; +import { ManualFeedback } from './components/manual-feedback/manual-feedback'; +import { AdminSkill } from './components/admin-skill/admin-skill'; +import { JobSearch } from './components/job-search/job-search'; +import { FullJobView } from './components/full-job-view/full-job-view'; @NgModule({ declarations: [ App, @@ -46,9 +54,24 @@ import { QuestionFillInTheBlank } from './components/question-fill-in-the-blank/ QuestionTrueFalse, QuestionMultipleChoice, QuestionCodeCompletion, - QuestionFillInTheBlank, + CompanyHome, + QuestionOpenEnded, + MyJobs, + ReviewUser, + ManualFeedback, + AdminSkill, + JobSearch, + FullJobView, + ], + imports: [ + BrowserModule, + AppRoutingModule, + ReactiveFormsModule, + FormsModule, + RouterModule, + CommonModule, + AsyncPipe, ], - imports: [BrowserModule, AppRoutingModule, ReactiveFormsModule, FormsModule], providers: [ provideBrowserGlobalErrorListeners(), provideHttpClient(withInterceptors([errorInterceptor])), diff --git a/web/src/app/app-routing-module.ts b/web/src/app/app-routing-module.ts index c85e21d..94e08a6 100644 --- a/web/src/app/app-routing-module.ts +++ b/web/src/app/app-routing-module.ts @@ -13,23 +13,97 @@ import { JobEdit } from './components/job-edit/job-edit'; import { AssessmentCreate } from './components/assessment-create/assessment-create'; import { JobDetail } from './components/job-detail/job-detail'; import { TestTake } from './components/test-take/test-take'; +import { CompanyHome } from './components/company-home/company-home'; +import { MyJobs } from './components/my-jobs/my-jobs'; +import { ReviewUser } from './components/review-user/review-user'; +import { ManualFeedback } from './components/manual-feedback/manual-feedback'; +import { AdminSkill } from './components/admin-skill/admin-skill'; +import { authGuard } from './interceptors/auth-guard'; +import { JobSearch } from './components/job-search/job-search'; +import { FullJobView } from './components/full-job-view/full-job-view'; const routes: Routes = [ { path: '', redirectTo: 'home', pathMatch: 'full' }, - { path: 'home', component: HomePage }, + + { path: 'home', component: HomePage}, + { path: 'login', component: Login }, { path: 'register', component: Register }, - { path: 'question-bank', component: QuestionBankList }, - { path: 'question-bank/create', component: QuestionBankForm }, - { path: 'question-bank/:id/edit', component: QuestionBankForm }, - { path: 'question-bank/:id', component: QuestionBankDetails }, - { path: 'editProfile', component: EditProfile }, - { path: 'viewProfile', component: ProfileView }, - { path: 'job-upload', component: Jobupload }, - { path: 'editJob/:id', component: JobEdit }, - { path: 'job/:id', component: JobDetail }, - { path: 'job/:id/test', component: TestTake }, - { path: 'assessments/create', component: AssessmentCreate}, + + { path: 'question-bank', component: QuestionBankList, + canActivate: [authGuard], + data: { roles: ['EMPLOYER'] } + }, + + { path: 'question-bank/create', component: QuestionBankForm, + canActivate: [authGuard], + data: { roles: ['EMPLOYER'] } + }, + + { path: 'question-bank/:id/edit', component: QuestionBankForm, + canActivate: [authGuard], + data: { roles: ['EMPLOYER'] } + }, + + { path: 'question-bank/:id', component: QuestionBankDetails, + canActivate: [authGuard], + data: { roles: ['EMPLOYER'] } + }, + + { path: 'editProfile', component: EditProfile, canActivate: [authGuard] }, + { path: 'viewProfile', component: ProfileView, canActivate: [authGuard] }, + + { path: 'job-upload', component: Jobupload, + canActivate: [authGuard], + data: { roles: ['EMPLOYER'] } + }, + + { path: 'editJob/:id', component: JobEdit, + canActivate: [authGuard], + data: { roles: ['EMPLOYER'] } + }, + + { path: 'job/:id', component: JobDetail + }, + + { path: 'job/:id/test', component: TestTake + }, + + { path: 'assessments/create', component: AssessmentCreate, + canActivate: [authGuard], + data: { roles: ['EMPLOYER'] } + }, + + { path: 'company', component: CompanyHome, + canActivate: [authGuard], + data: { roles: ['EMPLOYER'] } + }, + + { path: 'myJobs', component: MyJobs, + canActivate: [authGuard], + data: { roles: ['EMPLOYER'] } + }, + + { path: 'reviewUser/:id', component: ReviewUser, + canActivate: [authGuard], + data: { roles: ['EMPLOYER'] } + }, + + { path: 'manualFeedback/:id', component: ManualFeedback, + canActivate: [authGuard], + data: { roles: ['EMPLOYER'] } + }, + + { path: 'skills', component: AdminSkill}, + + { path: 'skill/:skillId/test/:assessmentId', component: TestTake, + canActivate: [authGuard] + }, + + {path: 'search', component: JobSearch}, + + { path: 'full-job-view/:id', component: FullJobView }, + { path: '**', redirectTo: 'home', pathMatch: 'full' }, ]; @@ -37,4 +111,4 @@ const routes: Routes = [ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) -export class AppRoutingModule {} +export class AppRoutingModule { } diff --git a/web/src/app/components/admin-skill/admin-skill.html b/web/src/app/components/admin-skill/admin-skill.html new file mode 100644 index 0000000..049e5ef --- /dev/null +++ b/web/src/app/components/admin-skill/admin-skill.html @@ -0,0 +1,49 @@ +
+

Add New Skill

+ +
{{ successMessage }}
+
{{ errorMessage }}
+ +
+
+ + +
+ Skill name is required and must be at least 2 characters. +
+
+ +
+ + +
+ No assessments available to assign. +
+ +
+
+ +
+
+
+ + +
+
\ No newline at end of file diff --git a/web/src/app/components/admin-skill/admin-skill.scss b/web/src/app/components/admin-skill/admin-skill.scss new file mode 100644 index 0000000..e69de29 diff --git a/web/src/app/components/admin-skill/admin-skill.spec.ts b/web/src/app/components/admin-skill/admin-skill.spec.ts new file mode 100644 index 0000000..6c863d8 --- /dev/null +++ b/web/src/app/components/admin-skill/admin-skill.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminSkill } from './admin-skill'; + +describe('AdminSkill', () => { + let component: AdminSkill; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [AdminSkill], + }).compileComponents(); + + fixture = TestBed.createComponent(AdminSkill); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/web/src/app/components/admin-skill/admin-skill.ts b/web/src/app/components/admin-skill/admin-skill.ts new file mode 100644 index 0000000..c17e54e --- /dev/null +++ b/web/src/app/components/admin-skill/admin-skill.ts @@ -0,0 +1,106 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { SkillService } from '../../services/skillservice'; +import { AssessmentService } from '../../services/assesmentservice'; +import { SkillCreateDto } from '../../Models/Dtos/Skill/skill-create-dto'; + +@Component({ + selector: 'app-admin-skill', + standalone: false, + templateUrl: './admin-skill.html', + styleUrl: './admin-skill.scss', +}) +export class AdminSkill implements OnInit{ + skillForm: FormGroup; + availableAssessments: any[] = []; + selectedAssessmentIds: Set = new Set(); + + isSubmitting = false; + successMessage = ''; + errorMessage = ''; + + constructor( + private fb: FormBuilder, + private skillService: SkillService, + private assessmentService: AssessmentService + ) { + this.skillForm = this.fb.group({ + name: ['', [Validators.required, Validators.minLength(2)]] + }); + } + + ngOnInit(): void { + this.assessmentService.getAllAssessments().subscribe({ + next: (assessments) => { + this.availableAssessments = assessments; + }, + error: (err) => console.error('Failed to load assessments', err) + }); + } + + toggleAssessment(assessmentId: string, event: Event): void { + const isChecked = (event.target as HTMLInputElement).checked; + if (isChecked) { + this.selectedAssessmentIds.add(assessmentId); + } else { + this.selectedAssessmentIds.delete(assessmentId); + } + } + + onSubmit(): void { + if (this.skillForm.invalid) return; + + this.isSubmitting = true; + this.successMessage = ''; + this.errorMessage = ''; + + const newSkill: SkillCreateDto = { + name: this.skillForm.value.name, + assesments: null + }; + + this.skillService.createSkill(newSkill).subscribe({ + next: (createdSkill) => { + const skillId = createdSkill.id; + console.log('Skillcreated', createdSkill); + + if (this.selectedAssessmentIds.size > 0) { + this.assignAssessmentsToSkill(skillId); + } else { + this.finishSubmission('Skill created successfully!'); + } + }, + error: (err) => { + this.errorMessage = 'Failed to create skill.'; + this.isSubmitting = false; + console.error(err); + } + }); + } + + private assignAssessmentsToSkill(skillId: string): void { + // Convert the Set of selected IDs to an Array + const assessmentIds = Array.from(this.selectedAssessmentIds); + + this.assessmentService.assignAssessmentsToSkill(assessmentIds, skillId).subscribe({ + next: () => { + this.finishSubmission('Skill created and assessments assigned successfully!'); + }, + error: (err) => { + this.errorMessage = 'Skill created, but failed to assign assessments.'; + console.error(err); + this.isSubmitting = false; + } + }); + } + + private finishSubmission(message: string): void { + this.successMessage = message; + this.isSubmitting = false; + this.skillForm.reset(); + this.selectedAssessmentIds.clear(); + + const checkboxes = document.querySelectorAll('.assessment-checkbox'); + checkboxes.forEach(cb => cb.checked = false); + } +} diff --git a/web/src/app/components/assessment-create/assessment-create.html b/web/src/app/components/assessment-create/assessment-create.html index 754fbac..983faf5 100644 --- a/web/src/app/components/assessment-create/assessment-create.html +++ b/web/src/app/components/assessment-create/assessment-create.html @@ -1,101 +1,280 @@ -
-
-

Create Test Template

-

Group questions together to create a reusable assessment.

- -
-
- - +
+
+
+
+

Create Test Template

+

Build a reusable assessment from fixed questions and randomized tag groups.

+
-
- - -
+ +
+ -
- - -
+ + + -
-
-
Selected Questions ({{ selectedQuestions.length }})
- +
+
- @if (selectedQuestions.length > 0) { -
- @for (q of selectedQuestions; track q.id) { -
-
- {{ q.type }} - {{ q.title }} -
- -
- } -
- } @else { -
-

Your assessment is empty. Add questions from the bank.

+
- @if (error) { -
{{ error }}
- } +
+
+

Questions

+ {{ plannedQuestions.length }} +
-
- - -
+ @if (plannedQuestions.length > 0) { +
+ @for (entry of plannedQuestions; track entry.source + '-' + entry.question.id + '-' + (entry.randomTag ?? '')) { +
+
+ {{ entry.question.title }} +

{{ getQuestionPreview(entry.question) }}

+
+ {{ getQuestionTypeLabel(entry.question.type) }} + {{ getDifficultyLabel(entry.question.difficulty) }} +
+
+ + @if (entry.source === 'random' && entry.randomTag) { + From tag: {{ entry.randomTag }} + } +
+ } +
+ } @else { +
No questions selected.
+ } +
+
-
+ @if (showQuestionModal) { - - } diff --git a/web/src/app/components/assessment-create/assessment-create.scss b/web/src/app/components/assessment-create/assessment-create.scss index e69de29..f052c49 100644 --- a/web/src/app/components/assessment-create/assessment-create.scss +++ b/web/src/app/components/assessment-create/assessment-create.scss @@ -0,0 +1,592 @@ +:host { + display: block; +} + +.ac-page, +.ac-modal-backdrop { + --ac-bg: #fdf0d5; + --ac-surface: #ffffff; + --ac-panel: #f9f9f9; + --ac-ink: #003049; + --ac-line: rgba(0, 48, 73, 0.18); + --ac-muted: rgba(0, 48, 73, 0.65); + --ac-tag-bg: rgba(102, 155, 188, 0.17); + --ac-success: #247a3b; + --ac-danger: #c12126; +} + +.ac-page { + background: var(--ac-bg); + color: var(--ac-ink); + min-height: calc(100vh - 56px); + padding: 2rem 1rem; +} + +.ac-shell { + width: min(1250px, 100%); + margin: 0 auto; +} + +.ac-header, +.ac-panel-heading, +.ac-block-title, +.ac-modal-header, +.ac-modal-footer, +.ac-question-title-row { + display: flex; + justify-content: space-between; + gap: 1rem; +} + +.ac-header { + margin-bottom: 1.2rem; +} + +.ac-header h2, +.ac-modal-header h3 { + margin: 0; + font-size: clamp(1.5rem, 1.1rem + 1.3vw, 2.2rem); + font-weight: 800; +} + +.ac-page p, +.ac-modal-dialog p { + color: var(--ac-muted); + font-weight: 600; +} + +.ac-layout { + display: grid; + grid-template-columns: minmax(0, 1.1fr) minmax(360px, 0.9fr); + gap: 1rem; + align-items: start; +} + +.ac-form-panel, +.ac-selection-panel, +.ac-picker-filters, +.ac-question-results, +.ac-tag-groups { + background: var(--ac-surface); + border: 1px solid rgba(0, 48, 73, 0.1); + border-radius: 18px; + box-shadow: 0 3px 14px rgba(0, 48, 73, 0.08); + padding: 1rem; +} + +.ac-field, +.ac-filter-field, +.ac-random-item label, +.ac-tag-group-actions label { + display: flex; + flex-direction: column; + gap: 0.35rem; + font-weight: 800; +} + +.ac-field { + margin-bottom: 1rem; +} + +.ac-page input, +.ac-page textarea, +.ac-page select, +.ac-modal-dialog input, +.ac-modal-dialog select { + width: 100%; + border: 1px solid rgba(0, 48, 73, 0.2); + border-radius: 11px; + background: #fff; + color: var(--ac-ink); + padding: 0.62rem 0.7rem; +} + +.ac-page textarea { + resize: vertical; + min-height: 130px; +} + +.ac-primary-btn, +.ac-save-btn, +.ac-secondary-btn, +.ac-clear-btn { + border-radius: 12px; + font-weight: 800; + padding: 0.64rem 0.95rem; +} + +.ac-primary-btn, +.ac-save-btn { + border: 2px solid var(--ac-ink); + background: var(--ac-ink); + color: #fff; +} + +.ac-secondary-btn, +.ac-clear-btn { + border: 1px solid rgba(0, 48, 73, 0.35); + background: transparent; + color: var(--ac-ink); +} + +.compact { + padding: 0.42rem 0.72rem !important; + font-size: 0.82rem; +} + +.ac-panel-heading .compact { + font-size: 1rem; +} + +.ac-submit-row { + display: flex; + flex-wrap: wrap; + gap: 0.65rem; + margin-top: 1.4rem; +} + +.ac-selection-block { + margin-top: 1rem; + border-top: 1px solid var(--ac-line); + padding-top: 1rem; +} + +.ac-panel-heading h3, +.ac-block-title h4, +.ac-side-heading h4 { + margin: 0; + font-size: 1.05rem; + font-weight: 800; +} + +.ac-block-title span, +.ac-meta-line span, +.ac-card-meta span, +.ac-tag-pill, +.ac-selected-badge, +.ac-random-badge, +.ac-selection-source-badge { + background: var(--ac-tag-bg); + border: 1px solid rgba(0, 48, 73, 0.15); + border-radius: 999px; + padding: 0.18rem 0.48rem; + font-size: 0.76rem; + font-weight: 800; +} + +.ac-selected-list, +.ac-random-list, +.ac-tag-group-list, +.ac-question-results { + display: flex; + flex-direction: column; + gap: 0.65rem; +} + +.ac-selected-item, +.ac-random-item, +.ac-tag-group-card { + position: relative; + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 0.75rem; + align-items: center; + border: 1px solid var(--ac-line); + border-radius: 14px; + background: var(--ac-panel); + padding: 0.75rem; +} + +.ac-random-item { + grid-template-columns: minmax(0, 1fr) 86px auto; +} + +.ac-selected-item.random-source { + padding-bottom: 2rem; +} + +.ac-selected-item p { + overflow-wrap: anywhere; +} + +.ac-meta-line, +.ac-card-meta, +.ac-question-badges { + display: flex; + flex-wrap: wrap; + gap: 0.35rem; +} + +.ac-meta-line, +.ac-card-meta { + margin-top: 0.5rem; +} + +.ac-selected-badge { + background: rgba(36, 122, 59, 0.15); + color: var(--ac-success); +} + +.ac-random-badge { + background: rgba(102, 155, 188, 0.2); + color: var(--ac-ink); +} + +.ac-selection-source-badge { + position: absolute; + right: 0.75rem; + bottom: 0.6rem; + background: rgba(102, 155, 188, 0.2); + color: var(--ac-ink); +} + +.ac-icon-btn, +.ac-close-btn { + border: 0; + background: transparent; + color: var(--ac-ink); + font-weight: 900; + line-height: 1; + font-size: 1.4rem; +} + +.ac-close-btn { + font-size: 2rem; +} + +.danger { + color: var(--ac-danger) !important; +} + +.danger-outline { + border-color: rgba(193, 33, 38, 0.45); + color: var(--ac-danger); +} + +.ac-empty-box, +.ac-alert, +.ac-picker-alert { + border-radius: 14px; + padding: 0.85rem 1rem; + font-weight: 700; +} + +.ac-empty-box { + margin-top: 0.75rem; + background: rgba(0, 48, 73, 0.05); + color: var(--ac-muted); +} + +.ac-alert, +.ac-picker-alert { + background: rgba(193, 33, 38, 0.1); + color: var(--ac-danger); + border: 1px solid rgba(193, 33, 38, 0.2); +} + +.ac-modal-backdrop { + position: fixed; + inset: 0; + z-index: 2000; + display: flex; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.34); + padding: 1rem; +} + +.ac-modal-dialog { + width: min(1280px, 100%); + height: calc(100vh - 2rem); + overflow: hidden; + background: #fff; + color: var(--ac-ink); + border-radius: 20px; + box-shadow: 0 24px 70px rgba(0, 0, 0, 0.26); + display: flex; + flex-direction: column; +} + +.ac-modal-header, +.ac-modal-footer { + background: #fff; + border-bottom: 1px solid var(--ac-line); + padding: 1.15rem 1.35rem; +} + +.ac-modal-footer { + border-top: 1px solid var(--ac-line); + border-bottom: 0; + align-items: center; +} + +.ac-picker-filters { + display: grid; + grid-template-columns: minmax(150px, 0.8fr) minmax(150px, 0.8fr) minmax(260px, 1.4fr) auto; + gap: 0.8rem; + align-items: end; + margin: 1.25rem 1.35rem 1rem; +} + +.ac-tag-filter, +.ac-tag-input-shell { + position: relative; +} + +.ac-tag-input-shell { + min-height: 42px; + border: 1px solid rgba(0, 48, 73, 0.2); + border-radius: 11px; + background: #fff; + padding: 0.4rem; +} + +.ac-tag-input-shell input { + border: 0; + padding: 0.2rem 0; + margin-top: 0.25rem; +} + +.ac-tag-chip-list { + display: flex; + flex-wrap: wrap; + gap: 0.35rem; +} + +.ac-tag-chip { + border: 1px solid rgba(0, 48, 73, 0.25); + border-radius: 999px; + background: var(--ac-tag-bg); + color: var(--ac-ink); + font-size: 0.76rem; + font-weight: 800; + padding: 0.2rem 0.48rem; +} + +.ac-tag-dropdown { + position: absolute; + left: 0; + right: 0; + top: calc(100% + 0.25rem); + z-index: 5; + max-height: 220px; + overflow: auto; + background: #fff; + border: 1px solid rgba(0, 48, 73, 0.2); + border-radius: 10px; + box-shadow: 0 6px 20px rgba(0, 48, 73, 0.12); +} + +.ac-tag-option { + width: 100%; + border: 0; + background: transparent; + color: var(--ac-ink); + text-align: left; + padding: 0.55rem 0.7rem; +} + +.ac-tag-option:hover { + background: rgba(0, 48, 73, 0.08); +} + +.ac-tag-empty { + padding: 0.6rem 0.7rem; + color: var(--ac-muted); +} + +.ac-picker-layout { + min-height: 0; + overflow: hidden; + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(320px, 0.42fr); + gap: 1rem; + padding: 0 1.35rem 1.35rem; + background: #fff; + flex: 1; +} + +.ac-question-results, +.ac-tag-groups { + height: 100%; + min-height: 0; + overflow-y: auto; + overscroll-behavior: contain; +} + +.ac-question-results { + padding-right: 0.75rem; +} + +.ac-question-card { + flex: 0 0 auto; + border: 1px solid var(--ac-line); + border-radius: 14px; + background: var(--ac-panel); + overflow: hidden; +} + +.ac-question-card.selected, +.ac-tag-group-card.reserved { + border-color: rgba(36, 122, 59, 0.4); + background: rgba(36, 122, 59, 0.07); +} + +.ac-question-card.random-preview:not(.selected) { + border-color: rgba(102, 155, 188, 0.45); + background: rgba(102, 155, 188, 0.08); +} + +.ac-question-body { + width: 100%; + min-height: 118px; + border: 0; + background: transparent; + color: var(--ac-ink); + display: grid; + grid-template-columns: minmax(0, 1fr) 86px; + gap: 0.85rem; + align-items: start; + padding: 0.85rem; + text-align: left; +} + +.ac-question-copy { + min-width: 0; +} + +.ac-question-copy p { + overflow-wrap: anywhere; +} + +.ac-question-title-row { + align-items: flex-start; + flex-wrap: wrap; +} + +.ac-question-title-row h4 { + min-width: 220px; + flex: 1; + margin: 0; + font-size: 1rem; + font-weight: 800; +} + +.ac-question-badges { + justify-content: flex-end; +} + +.ac-add-indicator { + justify-self: end; + min-width: 78px; + border: 2px solid var(--ac-ink); + border-radius: 11px; + padding: 0.34rem 0.62rem; + text-align: center; + font-weight: 800; +} + +.ac-tag-group-card { + grid-template-columns: minmax(0, 1fr) minmax(132px, auto); + align-items: start; +} + +.ac-tag-group-card span { + display: inline-block; + margin-top: 0.35rem; + font-size: 0.78rem; + font-weight: 800; + color: var(--ac-success); +} + +.ac-tag-group-actions { + display: grid; + grid-template-columns: minmax(72px, 1fr) auto; + gap: 0.45rem; + align-items: end; +} + +.ac-tag-group-actions label { + font-size: 0.72rem; +} + +.ac-tag-group-actions input, +.ac-tag-group-actions .compact { + height: 38px; +} + +.ac-tag-group-actions input { + padding: 0.35rem 0.45rem; +} + +.ac-tag-group-actions .compact { + white-space: nowrap; +} + +.ac-picker-alert { + margin: 0 1.35rem 1rem; +} + +@media (max-width: 1100px) { + .ac-layout, + .ac-picker-layout { + grid-template-columns: 1fr; + } + + .ac-modal-dialog { + overflow-y: auto; + } + + .ac-picker-layout { + overflow: visible; + } + + .ac-question-results, + .ac-tag-groups { + height: auto; + max-height: none; + overflow: visible; + } +} + +@media (max-width: 760px) { + .ac-header, + .ac-panel-heading, + .ac-modal-header, + .ac-modal-footer { + flex-direction: column; + } + + .ac-primary-btn, + .ac-save-btn, + .ac-secondary-btn { + width: 100%; + } + + .ac-picker-filters, + .ac-random-item, + .ac-selected-item, + .ac-tag-group-card, + .ac-question-body { + grid-template-columns: 1fr; + } + + .ac-modal-header, + .ac-modal-footer, + .ac-picker-layout { + padding-left: 1rem; + padding-right: 1rem; + } + + .ac-picker-filters { + margin: 1rem; + } + + .ac-tag-group-actions { + grid-template-columns: 1fr; + } + + .ac-add-indicator { + justify-self: start; + } +} diff --git a/web/src/app/components/assessment-create/assessment-create.ts b/web/src/app/components/assessment-create/assessment-create.ts index 2b6735c..23f6389 100644 --- a/web/src/app/components/assessment-create/assessment-create.ts +++ b/web/src/app/components/assessment-create/assessment-create.ts @@ -1,9 +1,34 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, HostListener, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { QuestionResponseDto } from '../../Models/Dtos/Question/question-response-dto'; +import { DifficultyLevel } from '../../Models/Enums/DifficultyLevel'; +import { QuestionType } from '../../Models/Enums/QuestionType'; import { QuestionBankService } from '../../services/question-bank-service'; import { AssessmentService } from '../../services/assesmentservice'; +interface RandomTagRule { + tag: string; + count: number; +} + +interface TagGroupSummary { + tag: string; + key: string; + total: number; + available: number; + reserved: number; +} + +interface ResolvedRandomRule { + rule: RandomTagRule; + questions: QuestionResponseDto[]; +} + +interface PlannedQuestion { + question: QuestionResponseDto; + source: 'fixed' | 'random'; + randomTag?: string; +} @Component({ selector: 'app-assessment-create', @@ -14,14 +39,40 @@ import { AssessmentService } from '../../services/assesmentservice'; export class AssessmentCreate implements OnInit { title = ''; description = ''; - difficultyLevel = 0; + difficultyLevel = -1; availableQuestions: QuestionResponseDto[] = []; + filteredQuestions: QuestionResponseDto[] = []; + availableTags: string[] = []; selectedQuestions: QuestionResponseDto[] = []; + randomTagRules: RandomTagRule[] = []; showQuestionModal = false; loading = false; + questionsLoading = false; error = ''; + questionPickerError = ''; + + filterType = ''; + filterDifficulty = ''; + tagQuery = ''; + selectedFilterTags: string[] = []; + showTagDropdown = false; + randomGroupDraftCounts: Record = {}; + randomPreviewQuestionIds: Record = {}; + + readonly questionTypeOptions = [ + { label: 'Multiple Choice', value: QuestionType.MultipleChoice }, + { label: 'Code Completion', value: QuestionType.CodeCompletion }, + { label: 'True / False', value: QuestionType.TrueFalse }, + { label: 'Open-Ended', value: QuestionType.OpenEnded }, + ]; + + readonly difficultyOptions = [ + { label: 'Junior', value: DifficultyLevel.Junior }, + { label: 'Medior', value: DifficultyLevel.Medior }, + { label: 'Senior', value: DifficultyLevel.Senior }, + ]; constructor( private assessmentService: AssessmentService, @@ -30,33 +81,466 @@ export class AssessmentCreate implements OnInit { ) {} ngOnInit(): void { - this.questionBankService.getAll().subscribe(); - this.questionBankService.questions$.subscribe((questions) => { - this.availableQuestions = questions; + this.loadActiveQuestions(); + } + + @HostListener('document:click', ['$event']) + onDocumentClick(event: MouseEvent): void { + const target = event.target as HTMLElement | null; + if (!target || target.closest('.ac-tag-filter')) { + return; + } + + this.showTagDropdown = false; + } + + @HostListener('document:keydown.escape') + onEscapeKey(): void { + if (this.showQuestionModal) { + this.closeQuestionModal(); + } + } + + get filteredTagSuggestions(): string[] { + const query = this.normalizeTag(this.tagQuery); + return this.availableTags + .filter( + (tag) => + !this.selectedFilterTags.some( + (selected) => this.normalizeTag(selected) === this.normalizeTag(tag), + ), + ) + .filter((tag) => query === '' || this.normalizeTag(tag).includes(query)) + .slice(0, 12); + } + + get tagGroups(): TagGroupSummary[] { + const groups = new Map(); + const normalizedSelectedTags = new Set( + this.selectedFilterTags.map((tag) => this.normalizeTag(tag)), + ); + const reservedQuestionIds = new Set( + this.resolveRandomRules(false).flatMap((entry) => + entry.questions.map((question) => question.id), + ), + ); + + this.targetDifficultyQuestions.forEach((question) => { + this.getUniqueTags(question).forEach((tag) => { + const key = this.normalizeTag(tag); + if (normalizedSelectedTags.size > 0 && !normalizedSelectedTags.has(key)) { + return; + } + + const existing = groups.get(key); + if (existing) { + existing.questions.push(question); + return; + } + + groups.set(key, { tag, questions: [question] }); + }); }); + + return Array.from(groups.entries()) + .map(([key, group]) => { + const selectedIds = new Set(this.selectedQuestions.map((question) => question.id)); + const total = group.questions.length; + const available = group.questions.filter( + (question) => !selectedIds.has(question.id) && !reservedQuestionIds.has(question.id), + ).length; + const reserved = this.randomTagRules + .filter((rule) => this.normalizeTag(rule.tag) === key) + .reduce((sum, rule) => sum + rule.count, 0); + + return { + tag: group.tag, + key, + total, + available, + reserved, + }; + }) + .sort((a, b) => { + if (b.total !== a.total) { + return b.total - a.total; + } + return a.tag.localeCompare(b.tag); + }); + } + + get totalQuestionCount(): number { + return ( + this.selectedQuestions.length + this.randomTagRules.reduce((sum, rule) => sum + rule.count, 0) + ); + } + + get plannedQuestions(): PlannedQuestion[] { + const fixedQuestions = this.selectedQuestions.map((question) => ({ + question, + source: 'fixed' as const, + })); + + const randomQuestions = this.resolveRandomRulesFromPreview().flatMap((entry) => + entry.questions.map((question) => ({ + question, + source: 'random' as const, + randomTag: entry.rule.tag, + })), + ); + + return [...fixedQuestions, ...randomQuestions]; + } + + get hasAssessmentQuestions(): boolean { + return this.totalQuestionCount > 0; + } + + private get targetDifficultyQuestions(): QuestionResponseDto[] { + if (Number(this.difficultyLevel) === -1) { + return this.availableQuestions; + } + const targetDifficulty = String(this.difficultyLevel); + return this.availableQuestions.filter( + (question) => String(question.difficulty) === targetDifficulty, + ); + } + + onTargetDifficultyChange(): void { + this.selectedQuestions = this.selectedQuestions.filter( + (question) => String(question.difficulty) === String(this.difficultyLevel), + ); + this.rebuildAvailableTags(); + this.applyFilters(); + this.clampRandomRules(); + this.refreshRandomPreview(); + this.questionPickerError = ''; + } + + onModalDialogClick(event: MouseEvent): void { + event.stopPropagation(); + const target = event.target as HTMLElement | null; + if (!target || target.closest('.ac-tag-filter')) { + return; + } + + this.showTagDropdown = false; } openQuestionModal(): void { + this.questionPickerError = ''; this.showQuestionModal = true; } closeQuestionModal(): void { this.showQuestionModal = false; + this.showTagDropdown = false; } addQuestion(question: QuestionResponseDto): void { - if (!this.selectedQuestions.find((q) => q.id === question.id)) { - this.selectedQuestions.push(question); + if (this.isQuestionSelected(question.id)) { + return; } + + this.selectedQuestions = [...this.selectedQuestions, question]; + this.questionPickerError = ''; + this.clampRandomRules(); + this.refreshRandomPreview(); } removeQuestion(id: string): void { this.selectedQuestions = this.selectedQuestions.filter((q) => q.id !== id); + this.questionPickerError = ''; + this.refreshRandomPreview(); + } + + removePlannedQuestion(entry: PlannedQuestion): void { + if (entry.source === 'fixed') { + this.removeQuestion(entry.question.id); + return; + } + + if (entry.randomTag) { + this.removeRandomPreviewQuestion(entry.question.id, entry.randomTag); + } + } + + toggleQuestion(question: QuestionResponseDto): void { + if (this.isQuestionSelected(question.id)) { + this.removeQuestion(question.id); + return; + } + + this.addQuestion(question); + } + + isQuestionSelected(id: string): boolean { + return this.selectedQuestions.some((question) => question.id === id); + } + + addRandomRule(tag: string, requestedCount?: string | number): void { + const maxCount = this.getRandomGroupMaxCount(tag); + const existing = this.randomTagRules.find( + (rule) => this.normalizeTag(rule.tag) === this.normalizeTag(tag), + ); + + if (maxCount <= 0 && !existing) { + this.questionPickerError = `There are no more available questions with the "${tag}" tag.`; + return; + } + + const parsedCount = requestedCount === undefined ? 1 : Math.floor(Number(requestedCount)); + const targetCount = Math.max( + 0, + Math.min(Number.isFinite(parsedCount) ? parsedCount : 1, maxCount), + ); + + if (existing) { + this.updateRandomRuleCount(existing.tag, targetCount); + return; + } + + if (targetCount === 0) { + return; + } + + this.randomTagRules = [...this.randomTagRules, { tag, count: targetCount }]; + this.questionPickerError = ''; + this.refreshRandomPreview(); + } + + updateRandomRuleCount(tag: string, value: string | number): void { + const numericValue = Number(value); + const nextCount = Number.isFinite(numericValue) ? Math.floor(numericValue) : 0; + const maxCount = this.getRandomGroupMaxCount(tag); + const clampedCount = Math.max(0, Math.min(nextCount, maxCount)); + + if (clampedCount === 0) { + this.removeRandomRule(tag); + return; + } + + this.randomTagRules = this.randomTagRules.map((rule) => + this.normalizeTag(rule.tag) === this.normalizeTag(tag) + ? { ...rule, count: clampedCount } + : rule, + ); + this.clampRandomRules(); + this.refreshRandomPreview(); + this.questionPickerError = ''; + } + + removeRandomRule(tag: string): void { + const normalizedTag = this.normalizeTag(tag); + this.randomTagRules = this.randomTagRules.filter( + (rule) => this.normalizeTag(rule.tag) !== normalizedTag, + ); + const { [normalizedTag]: _removed, ...nextPreview } = this.randomPreviewQuestionIds; + this.randomPreviewQuestionIds = nextPreview; + this.questionPickerError = ''; + this.refreshRandomPreview(); + } + + removeRandomPreviewQuestion(questionId: string, tag: string): void { + const normalizedTag = this.normalizeTag(tag); + const currentRule = this.randomTagRules.find( + (rule) => this.normalizeTag(rule.tag) === normalizedTag, + ); + if (!currentRule) { + return; + } + + const nextCount = currentRule.count - 1; + if (nextCount <= 0) { + this.removeRandomRule(tag); + return; + } + + this.randomPreviewQuestionIds = { + ...this.randomPreviewQuestionIds, + [normalizedTag]: (this.randomPreviewQuestionIds[normalizedTag] ?? []).filter( + (id) => id !== questionId, + ), + }; + this.randomTagRules = this.randomTagRules.map((rule) => + this.normalizeTag(rule.tag) === normalizedTag ? { ...rule, count: nextCount } : rule, + ); + this.questionPickerError = ''; + this.clampRandomRules(); + this.refreshRandomPreview(); + } + + getRuleMaxCount(tag: string): number { + return this.getAvailableQuestionCountForRule(tag); + } + + getTagAvailableCount(tag: string): number { + return this.getTagGroup(tag)?.available ?? 0; + } + + getTagTotalCount(tag: string): number { + return this.getTagGroup(tag)?.total ?? 0; + } + + getRandomGroupDraftCount(tag: string): number { + const key = this.normalizeTag(tag); + const existing = this.randomTagRules.find((rule) => this.normalizeTag(rule.tag) === key); + if (existing) { + return existing.count; + } + + const value = this.randomGroupDraftCounts[key] ?? 1; + return Math.max(1, Math.min(value, Math.max(this.getRandomGroupMaxCount(tag), 1))); + } + + getRandomGroupMaxCount(tag: string): number { + return this.getAvailableQuestionCountForRule(tag); + } + + hasRandomRule(tag: string): boolean { + const normalizedTag = this.normalizeTag(tag); + return this.randomTagRules.some((rule) => this.normalizeTag(rule.tag) === normalizedTag); + } + + setRandomGroupDraftCount(tag: string, value: string | number | null): void { + if (value === '' || value === null) { + return; + } + + const key = this.normalizeTag(tag); + const existing = this.randomTagRules.find((rule) => this.normalizeTag(rule.tag) === key); + const parsedValue = Math.floor(Number(value)); + const max = this.getRandomGroupMaxCount(tag); + const nextValue = Math.max( + existing ? 0 : 1, + Math.min(Number.isFinite(parsedValue) ? parsedValue : 1, Math.max(max, 1)), + ); + + if (existing) { + this.updateRandomRuleCount(tag, nextValue); + return; + } + + this.randomGroupDraftCounts = { + ...this.randomGroupDraftCounts, + [key]: nextValue, + }; + } + + isQuestionInRandomPreview(questionId: string): boolean { + return Object.values(this.randomPreviewQuestionIds).some((ids) => ids.includes(questionId)); + } + + getRandomPreviewLabel(questionId: string): string { + const rule = this.randomTagRules.find((entry) => + (this.randomPreviewQuestionIds[this.normalizeTag(entry.tag)] ?? []).includes(questionId), + ); + return rule ? `From tag: ${rule.tag}` : ''; + } + + onFilterChange(): void { + this.applyFilters(); + } + + onTagInputFocus(): void { + this.showTagDropdown = true; + } + + onTagInputChange(value: string): void { + this.tagQuery = value; + this.showTagDropdown = true; + } + + onTagInputKeydown(event: KeyboardEvent): void { + if (event.key === 'Enter') { + event.preventDefault(); + const [firstSuggestion] = this.filteredTagSuggestions; + if (firstSuggestion) { + this.selectFilterTag(firstSuggestion); + } + return; + } + + if ( + event.key === 'Backspace' && + this.tagQuery.trim() === '' && + this.selectedFilterTags.length > 0 + ) { + this.removeFilterTag(this.selectedFilterTags[this.selectedFilterTags.length - 1]); + return; + } + + if (event.key === 'Escape') { + this.showTagDropdown = false; + } + } + + selectFilterTag(tag: string): void { + const normalizedTag = this.normalizeTag(tag); + if (normalizedTag === '') { + return; + } + + const alreadySelected = this.selectedFilterTags.some( + (selected) => this.normalizeTag(selected) === normalizedTag, + ); + if (!alreadySelected) { + this.selectedFilterTags = [...this.selectedFilterTags, tag.trim()]; + } + + this.tagQuery = ''; + this.showTagDropdown = true; + this.applyFilters(); + } + + removeFilterTag(tag: string): void { + const normalizedTag = this.normalizeTag(tag); + this.selectedFilterTags = this.selectedFilterTags.filter( + (selected) => this.normalizeTag(selected) !== normalizedTag, + ); + this.applyFilters(); + } + + clearFilters(): void { + this.filterType = ''; + this.filterDifficulty = ''; + this.tagQuery = ''; + this.selectedFilterTags = []; + this.showTagDropdown = false; + this.applyFilters(); + } + + getQuestionTypeLabel(type: QuestionType): string { + return this.questionTypeOptions.find((option) => option.value === type)?.label ?? String(type); + } + + getDifficultyLabel(difficulty: DifficultyLevel): string { + return ( + this.difficultyOptions.find((option) => option.value === difficulty)?.label ?? + String(difficulty) + ); + } + + getQuestionPreview(question: QuestionResponseDto): string { + const text = question.questionText?.trim(); + return text ? text : 'No question text provided.'; } onSubmit(): void { - if (!this.title || this.selectedQuestions.length === 0) { - this.error = 'Please provide a title and select at least one question.'; + this.error = ''; + + if (!this.title.trim()) { + this.error = 'Please provide a title.'; + return; + } + + if (!this.hasAssessmentQuestions) { + this.error = 'Please select at least one fixed question or random tag group.'; + return; + } + + const resolved = this.resolveQuestionSelection(); + if (!resolved) { return; } @@ -65,7 +549,7 @@ export class AssessmentCreate implements OnInit { title: this.title, description: this.description, difficultyLevel: Number(this.difficultyLevel), - questionIds: this.selectedQuestions.map((q) => q.id), + questionIds: resolved, }; this.assessmentService.createAssessment(dto).subscribe({ @@ -78,4 +562,230 @@ export class AssessmentCreate implements OnInit { }, }); } + + previewRandomRule(rule: RandomTagRule): QuestionResponseDto[] { + const resolved = this.resolveRandomRules(false); + return ( + resolved.find((entry) => this.normalizeTag(entry.rule.tag) === this.normalizeTag(rule.tag)) + ?.questions ?? [] + ); + } + + private loadActiveQuestions(): void { + this.questionsLoading = true; + + this.questionBankService.getAll({ isActive: true }).subscribe({ + next: (questions) => { + this.availableQuestions = questions.filter((question) => question.isActive); + this.rebuildAvailableTags(); + this.applyFilters(); + this.clampRandomRules(); + this.refreshRandomPreview(); + this.questionsLoading = false; + }, + error: () => { + this.availableQuestions = []; + this.filteredQuestions = []; + this.availableTags = []; + this.questionsLoading = false; + this.error = 'Failed to load active questions.'; + }, + }); + } + + private applyFilters(): void { + const selectedType = this.filterType.trim(); + const selectedDifficulty = this.filterDifficulty.trim(); + const normalizedSelectedTags = this.selectedFilterTags.map((tag) => this.normalizeTag(tag)); + + this.filteredQuestions = this.targetDifficultyQuestions.filter((question) => { + if (selectedType !== '' && question.type !== selectedType) { + return false; + } + + if (selectedDifficulty !== '' && String(question.difficulty) !== selectedDifficulty) { + return false; + } + + if (normalizedSelectedTags.length > 0) { + const questionTagSet = new Set( + this.getUniqueTags(question).map((tag) => this.normalizeTag(tag)), + ); + const hasAllTags = normalizedSelectedTags.every((tag) => questionTagSet.has(tag)); + if (!hasAllTags) { + return false; + } + } + + return true; + }); + } + + private rebuildAvailableTags(): void { + const tagsByNormalizedValue = new Map(); + + this.targetDifficultyQuestions.forEach((question) => { + this.getUniqueTags(question).forEach((tag) => { + const normalizedTag = this.normalizeTag(tag); + if (normalizedTag === '' || tagsByNormalizedValue.has(normalizedTag)) { + return; + } + + tagsByNormalizedValue.set(normalizedTag, tag); + }); + }); + + this.availableTags = Array.from(tagsByNormalizedValue.values()).sort((a, b) => + a.localeCompare(b), + ); + } + + private resolveQuestionSelection(): string[] | null { + const selectedIds = this.selectedQuestions.map((question) => question.id); + const usedIds = new Set(selectedIds); + const resolvedRandomRules = this.resolveRandomRulesFromPreview(); + + for (const entry of resolvedRandomRules) { + if (entry.questions.length !== entry.rule.count) { + this.error = `The "${entry.rule.tag}" random group needs ${entry.rule.count} questions, but only ${entry.questions.length} are available.`; + return null; + } + + entry.questions.forEach((question) => usedIds.add(question.id)); + } + + return Array.from(usedIds); + } + + private resolveRandomRules(randomize: boolean): ResolvedRandomRule[] { + const usedIds = new Set(this.selectedQuestions.map((question) => question.id)); + + return this.randomTagRules.map((rule) => { + const candidates = this.targetDifficultyQuestions.filter((question) => { + if (usedIds.has(question.id)) { + return false; + } + + return this.getUniqueTags(question).some( + (tag) => this.normalizeTag(tag) === this.normalizeTag(rule.tag), + ); + }); + + const orderedCandidates = randomize ? this.shuffleQuestions(candidates) : candidates; + const questions = orderedCandidates.slice(0, rule.count); + questions.forEach((question) => usedIds.add(question.id)); + + return { rule, questions }; + }); + } + + private clampRandomRules(): void { + const usedIds = new Set(this.selectedQuestions.map((question) => question.id)); + const nextRules: RandomTagRule[] = []; + + this.randomTagRules.forEach((rule) => { + const candidates = this.getQuestionsByTag(rule.tag).filter( + (question) => !usedIds.has(question.id), + ); + const count = Math.min(rule.count, candidates.length); + if (count <= 0) { + return; + } + + candidates.slice(0, count).forEach((question) => usedIds.add(question.id)); + nextRules.push({ ...rule, count }); + }); + + this.randomTagRules = nextRules; + } + + private refreshRandomPreview(): void { + const usedIds = new Set(this.selectedQuestions.map((question) => question.id)); + const nextPreview: Record = {}; + + this.randomTagRules.forEach((rule) => { + const key = this.normalizeTag(rule.tag); + const existingIds = this.randomPreviewQuestionIds[key] ?? []; + const candidates = this.getQuestionsByTag(rule.tag).filter( + (question) => !usedIds.has(question.id), + ); + const candidateIds = new Set(candidates.map((question) => question.id)); + const keptIds = existingIds.filter((id) => candidateIds.has(id)).slice(0, rule.count); + const keptIdSet = new Set(keptIds); + const missingCount = rule.count - keptIds.length; + const fillIds = this.shuffleQuestions( + candidates.filter((question) => !keptIdSet.has(question.id)), + ) + .slice(0, missingCount) + .map((question) => question.id); + + nextPreview[key] = [...keptIds, ...fillIds]; + nextPreview[key].forEach((id) => usedIds.add(id)); + }); + + this.randomPreviewQuestionIds = nextPreview; + } + + private resolveRandomRulesFromPreview(): ResolvedRandomRule[] { + return this.randomTagRules.map((rule) => { + const ids = this.randomPreviewQuestionIds[this.normalizeTag(rule.tag)] ?? []; + const questions = ids + .map((id) => this.targetDifficultyQuestions.find((question) => question.id === id)) + .filter((question): question is QuestionResponseDto => Boolean(question)); + + return { rule, questions }; + }); + } + + private getTagGroup(tag: string): TagGroupSummary | undefined { + const normalizedTag = this.normalizeTag(tag); + return this.tagGroups.find((group) => group.key === normalizedTag); + } + + private getAvailableQuestionCountForRule(tag: string): number { + const normalizedTag = this.normalizeTag(tag); + const usedIds = new Set(this.selectedQuestions.map((question) => question.id)); + + for (const rule of this.randomTagRules) { + if (this.normalizeTag(rule.tag) === normalizedTag) { + break; + } + + this.getQuestionsByTag(rule.tag) + .filter((question) => !usedIds.has(question.id)) + .slice(0, rule.count) + .forEach((question) => usedIds.add(question.id)); + } + + return this.getQuestionsByTag(tag).filter((question) => !usedIds.has(question.id)).length; + } + + private getQuestionsByTag(tag: string): QuestionResponseDto[] { + const normalizedTag = this.normalizeTag(tag); + return this.targetDifficultyQuestions.filter((question) => + this.getUniqueTags(question).some( + (questionTag) => this.normalizeTag(questionTag) === normalizedTag, + ), + ); + } + + private getUniqueTags(question: QuestionResponseDto): string[] { + return Array.from( + new Set((question.tags ?? []).map((tag) => tag.trim()).filter((tag) => tag.length > 0)), + ); + } + + private normalizeTag(value: string): string { + return value.trim().toLocaleLowerCase(); + } + + private shuffleQuestions(questions: QuestionResponseDto[]): QuestionResponseDto[] { + const shuffled = [...questions]; + for (let i = shuffled.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; + } + + return shuffled; + } } diff --git a/web/src/app/components/company-home/company-home.html b/web/src/app/components/company-home/company-home.html new file mode 100644 index 0000000..9b63b90 --- /dev/null +++ b/web/src/app/components/company-home/company-home.html @@ -0,0 +1,97 @@ +
+
+
+ + + +
+ + +
+ +
+ @if (companyJobs$ | async; as jobs) { + @if (jobs.length > 0) { + @for (job of jobs; track job.id) { +
+
+ +

{{job.title}}

+ + +
+ @for(tag of job.tags; track $index){ + + {{tag}} + + } +
+ +

{{job.shortDescription}}

+ +
+ {{job.location}} + {{ getTimeAgo(job.createdAt) }} +
+
+
+ } + } + } +
+ + +
+ @if (selectedJob) { +
+
+ + +
+

{{selectedJob.title}}

+ + + +
+ +
+

{{selectedJob.shortDescription}}

+
+ +
+
Job characteristics
+
+ + {{selectedJob.employmentType}} + + @for(tag of selectedJob.tags; track $index){ + + {{tag}} + + } +
+ +
+
Office location
+

{{selectedJob.location}}

+ +
+
+ } @else { + +
+ +
Select a job to see details
+
+ } +
+ +
+
+
\ No newline at end of file diff --git a/web/src/app/components/company-home/company-home.scss b/web/src/app/components/company-home/company-home.scss new file mode 100644 index 0000000..4b504c3 --- /dev/null +++ b/web/src/app/components/company-home/company-home.scss @@ -0,0 +1,30 @@ +.button-medium { + width: 50%; + justify-content: center; + border: 0px; +} + +.bi { + -webkit-text-stroke: 0.5px; +} + +.color { + color: #003049; +} + +.radius { + border-radius: 18px; + cursor: pointer; +} + +.border-highlight { + border: 1px solid #003049 !important; +} + +.weight { + font-weight: 500 !important; +} + +.sample { + height: 150px; +} \ No newline at end of file diff --git a/web/src/app/components/company-home/company-home.spec.ts b/web/src/app/components/company-home/company-home.spec.ts new file mode 100644 index 0000000..62dc309 --- /dev/null +++ b/web/src/app/components/company-home/company-home.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CompanyHome } from './company-home'; + +describe('CompanyHome', () => { + let component: CompanyHome; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [CompanyHome], + }).compileComponents(); + + fixture = TestBed.createComponent(CompanyHome); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/web/src/app/components/company-home/company-home.ts b/web/src/app/components/company-home/company-home.ts new file mode 100644 index 0000000..f626c1f --- /dev/null +++ b/web/src/app/components/company-home/company-home.ts @@ -0,0 +1,106 @@ +import { Component, OnInit } from '@angular/core'; +import { Observable, filter, switchMap, of, map } from 'rxjs'; +import { JobViewDto } from '../../Models/Dtos/Job/JobView-dto'; +import { JobService } from '../../services/job-service'; +import { ProfileViewDto } from '../../Models/Dtos/User/profile-view-dto'; +import { ProfileService } from '../../services/profile-service'; + +@Component({ + selector: 'app-company-home', + standalone: false, + templateUrl: './company-home.html', + styleUrl: './company-home.scss', +}) +export class CompanyHome implements OnInit { + profile$!: Observable; + companyJobs$!: Observable; + selectedJob: JobViewDto | null = null; + + constructor( + private jobService: JobService, + private profileService: ProfileService, + ) {} + + ngOnInit(): void { + this.profile$ = this.profileService.currentProfile$; + + this.companyJobs$ = this.profile$.pipe( + filter((profile): profile is ProfileViewDto => profile !== null), + switchMap((profile) => { + if (!profile.companyId) { + return of([]); + } + return this.jobService.getJobsByCompanyId(profile.companyId); + }), + map((jobs) => this.parseJobsTags(jobs)) + ); + if(!this.selectedJob) { + this.companyJobs$.subscribe(jobs => { this.selectedJob = jobs[0] ?? null}) + } + } + + getTimeAgo(dateStr: string): string { + const formattedDateStr = + dateStr.includes('Z') || dateStr.includes('+') ? dateStr : `${dateStr.replace(' ', 'T')}Z`; + const date = new Date(formattedDateStr); + const time = date.getTime(); + + if (!dateStr || Number.isNaN(time)) { + return 'Unknown'; + } + + const diffMs = Date.now() - time; + if (diffMs < 0) { + return 'Just now'; + } + + const minutes = Math.floor(diffMs / 60000); + if (minutes < 1) return 'Just now'; + if (minutes < 60) return `${minutes} minute${minutes === 1 ? '' : 's'} ago`; + + const hours = Math.floor(minutes / 60); + if (hours < 24) return `${hours} hour${hours === 1 ? '' : 's'} ago`; + + const days = Math.floor(hours / 24); + if (days < 30) return `${days} day${days === 1 ? '' : 's'} ago`; + + const months = Math.floor(days / 30); + if (months < 12) return `${months} month${months === 1 ? '' : 's'} ago`; + + const years = Math.floor(days / 365); + return `${years} year${years === 1 ? '' : 's'} ago`; + } + + selectJob(job: JobViewDto): void { + this.selectedJob = job; + } + + private parseJobsTags(jobs: JobViewDto[]): JobViewDto[] { + return jobs.map((job) => { + const parsedJob = { ...job }; + if (typeof parsedJob.tags === 'string') { + const tagString = parsedJob.tags as unknown as string; + parsedJob.tags = tagString + .split(',') + .map((t) => t + .trim() + .replace(/[\[\]"']/g, '') + ) + .filter((t) => t !== '') + } + if (Array.isArray(parsedJob.tags)) { + parsedJob.tags = parsedJob.tags + .map((t) => + typeof t === 'string' ? + t.replace(/[\[\]"']/g, '').trim() + : t + ) + } + if (!Array.isArray(parsedJob.tags)) { + parsedJob.tags = []; + } + + return parsedJob + }) + } +} diff --git a/web/src/app/components/edit-profile/edit-profile.html b/web/src/app/components/edit-profile/edit-profile.html index 6478d56..c925a39 100644 --- a/web/src/app/components/edit-profile/edit-profile.html +++ b/web/src/app/components/edit-profile/edit-profile.html @@ -1,49 +1,254 @@ -
+
+ + +
+

Edit Profile

+
Update your profile details, skills, education, and experience.
-
- -

Edit Profile

+
+
+ + +
- +
+
+ + +
-
- - +
+ + +
-
- - +
+ +
-
- - +
+ +
-
- - +
+ +
+ + +
+ -
- - +
+
+

Your Skills

-
- - +
+ @for (skill of userSkills; track skill.id) { +
+ {{ skill.name }} + +
+ }
+

Add a skill

+
+ -
-
+
- +
+
+

Your Education

+ +
+ @if (educations.length > 0) { +
+ @for (education of educations; track $index) { +
+
+

{{ education.school }}

+
{{ education.fieldOfStudy }}
+

{{ education.degree }}

+ @if(education.isOngoing) { +

Ongoing

+ } +

+ {{ education.startDate | date:'yyyy-MM-dd' }} - {{ education.endDate ? (education.endDate | + date:'yyyy-MM-dd') : 'Ongoing' }} +

+
+ +
+ +
+
+ } +
+ } @else { +

No education entries yet.

+ } +
+ +
+
+

Your Experiences

+ +
+ + @if (experiences.length > 0) { +
+ @for (experience of experiences; track $index) { +
+
+

{{ experience.companyName }}

+
{{ experience.jobTitle }}
+

+ {{ experience.startDate | date:'yyyy-MM-dd' }} - {{ experience.endDate | date:'yyyy-MM-dd' }} +

+
+ +
+ +
+
+ + } +
+ } @else { +

No experience entries yet.

+ } +
-
\ No newline at end of file + @if (showExperienceModal) { + + + } + + @if (showEducationModal) { + + + } +
\ No newline at end of file diff --git a/web/src/app/components/edit-profile/edit-profile.scss b/web/src/app/components/edit-profile/edit-profile.scss index e69de29..bb306f1 100644 --- a/web/src/app/components/edit-profile/edit-profile.scss +++ b/web/src/app/components/edit-profile/edit-profile.scss @@ -0,0 +1,248 @@ +$bg-color: #FDF0D5; +$card-bg: #ffffff; +$text-main: #003049; +$text-muted: #5e6c79; +$btn-blue: #003049; +$btn-blue-hover: #0a5680; +$border-color: #003049; +$input-bg: #ffffff; +$red-accent: #C1121F; + +:host { + display: flex; + flex-direction: column; + align-items: center; + min-height: 100vh; + background-color: $bg-color; + color: $text-main; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + width: 100%; + + * { + box-sizing: border-box; + } +} + +.container { + width: 100%; + max-width: 800px; + padding: 20px; + margin-top: 20px; +} + +.form-card { + background-color: $card-bg; + border-radius: 12px; + padding: 40px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); +} + +.form-title { + font-size: 28px; + color: $text-main; + margin-bottom: 8px; +} + +.form-subtitle { + color: $text-muted; + font-size: 14px; + margin-bottom: 30px; +} + +.section-card { + margin-top: 1.5rem; +} + +.section-card h3 { + margin: 0 0 0.75rem 0; + font-size: 1.15rem; + font-weight: 700; + color: $text-main; +} + +.card-list { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.75rem; + padding: 0.75rem; + border: 2px solid $border-color; + border-radius: 10px; +} + +.card-list > .education-card, +.card-list > .experience-card, +.card-list > .skill-chip { + width: 100%; + max-width: 740px; + box-sizing: border-box; + margin: 0; +} + +.skill-chip, +.education-card, +.experience-card { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1rem; + border-radius: 10px; + border: 2px solid $border-color; + background: #fff; + margin-bottom: 0.75rem; +} + +.education-card .card-left, +.experience-card .card-left { + flex: 1 1 auto; + min-width: 0; + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.education-card .card-actions, +.experience-card .card-actions { + width: 96px; + display: flex; + align-items: center; + justify-content: center; +} + +.education-card h4, +.experience-card h4, +.education-card h5, +.experience-card h5 { + margin: 0; + line-height: 1.15; +} + +.education-card h4, +.experience-card h4 { + font-size: 1rem; + color: $text-main; + font-weight: 700; +} + +.education-card h5, +.experience-card h5 { + font-size: 0.95rem; + color: #4b5963; + font-weight: 600; +} + +.skill-chip { + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.5rem; +} + +.edit-link, +.click { + cursor: pointer; + text-decoration: none; + color: $text-main; + font-weight: 600; +} + +.search-btn, +.submit-btn { + background-color: $btn-blue; + color: #ffffff; + border: none; + border-radius: 8px; + padding: 14px 24px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: background-color 0.2s; +} + +.search-btn:hover, +.submit-btn:hover { + background-color: $btn-blue-hover; +} + +.form-control, +input, +select, +textarea { + width: 100%; + padding: 12px 16px; + border: 2px solid $border-color; + border-radius: 8px; + background-color: $input-bg; + font-size: 16px; + color: $text-main; + outline: none; + transition: border-color 0.2s; + appearance: none; +} + +.form-control:focus, +input:focus, +select:focus, +textarea:focus { + border-color: $red-accent; +} + +.modal-backdrop { + background-color: rgba(0, 0, 0, 0.5); +} + +.modal-content { + border-radius: 12px; + border: 2px solid $border-color; + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.12); +} + +.modal-header, +.modal-footer { + border-color: rgba(0, 48, 73, 0.12); +} + +.modal-title { + color: $text-main; + font-weight: 700; +} + +.file-row { + width: 100%; +} + +.file-input-inline { + flex: 1 1 auto; + min-width: 0; +} + +.search-btn.btn-sm { + padding: 8px 12px; + height: 40px; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.education-card, +.experience-card { + padding: 0.75rem 1rem; +} + +@media (max-width: 576px) { + .education-card, + .experience-card { + flex-direction: column; + align-items: flex-start; + } + .education-card .card-actions, + .experience-card .card-actions { + width: 100%; + justify-content: flex-end; + margin-top: 0.5rem; + } + + .search-btn.btn-sm { + height: 38px; + } +} \ No newline at end of file diff --git a/web/src/app/components/edit-profile/edit-profile.ts b/web/src/app/components/edit-profile/edit-profile.ts index 87dd33a..417c0d7 100644 --- a/web/src/app/components/edit-profile/edit-profile.ts +++ b/web/src/app/components/edit-profile/edit-profile.ts @@ -1,11 +1,18 @@ -import { Component, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { ProfileService } from '../../services/profile-service'; import { AuthService } from '../../services/auth-service'; import { HttpClient } from '@angular/common/http'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { environment } from '../../../environments/environment.development'; import { Router } from '@angular/router'; - +import { SkillViewDto } from '../../Models/Dtos/Skill/skill-view-dto'; +import { SkillService } from '../../services/skillservice'; +import { EducationViewDto } from '../../Models/Dtos/Education/EducationViewDto'; +import { ExperienceCreateDto } from '../../Models/Dtos/Experience/ExperienceCreateDto'; +import { EducationCreateDto } from '../../Models/Dtos/Education/EducationCreateDto'; +import { ExperienceService } from '../../services/experience-service'; +import { EducationService } from '../../services/education-service'; +import { ExperienceViewDto } from '../../Models/Dtos/Experience/ExperienceViewDto'; @Component({ selector: 'app-edit-profile', standalone: false, @@ -13,38 +20,89 @@ import { Router } from '@angular/router'; styleUrl: './edit-profile.scss', }) export class EditProfile implements OnInit { - constructor( private fb: FormBuilder, private profileService: ProfileService, private authService: AuthService, private http: HttpClient, - private router:Router - ){} + private router: Router, + private skillService: SkillService, + private educationService: EducationService, + private experienceService: ExperienceService, + private cdr: ChangeDetectorRef, + ) {} form!: FormGroup; selectedImageBase64: string | null = null; + userSkills: { id: string; name: string }[] = []; + availableSkills: SkillViewDto[] = []; + + pendingSkillAdditions = new Set(); + + showEducationModal = false; + showExperienceModal = false; + + educations: EducationViewDto[] = []; + experiences: ExperienceViewDto[] = []; + + experienceForm!: FormGroup; + educationForm!: FormGroup; + ngOnInit(): void { this.form = this.fb.group({ email: ['', Validators.required], firstName: ['', Validators.required], lastName: ['', Validators.required], headline: [''], - bio: [''] + bio: [''], + }); + + this.experienceForm = this.fb.group({ + jobTitle: ['', Validators.required], + companyName: ['', Validators.required], + startDate: ['', Validators.required], + endDate: [''], + }); + + this.educationForm = this.fb.group({ + school: ['', Validators.required], + degree: ['', Validators.required], + fieldOfStudy: ['', Validators.required], + startDate: ['', Validators.required], + endDate: [''], + description: [''], }); - this.profileService.currentProfile$.subscribe(profile => { - if (profile) { - this.form.patchValue({ - email: profile.email, - firstName: profile.fullName?.split(' ')[0] || '', - lastName: profile.fullName?.split(' ')[1] || '', - headline: profile.headline, - bio: profile.bio - }); + this.profileService.currentProfile$.subscribe((profile) => { + if (!profile) return; + + this.form.patchValue({ + email: profile.email, + firstName: profile.fullName?.split(' ')[0] || '', + lastName: profile.fullName?.split(' ')[1] || '', + headline: profile.headline, + bio: profile.bio, + }); + + if (this.userSkills.length === 0 && profile.skills) { + this.userSkills = + typeof profile.skills[0] === 'string' + ? (profile.skills as any as string[]).map((s) => ({ id: s, name: s })) + : ([...profile.skills] as any); } + + this.cdr.detectChanges(); }); + + this.getAllSkills(); + + const userId = this.authService.getUserId(); + if (userId) { + this.loadEducations(); + this.loadExperiences(); + this.profileService.loadProfile(userId); + } } onFileSelected(event: any) { @@ -60,7 +118,6 @@ export class EditProfile implements OnInit { reader.onload = () => { const base64 = reader.result as string; - this.selectedImageBase64 = base64.split(',')[1]; }; @@ -94,20 +151,199 @@ export class EditProfile implements OnInit { dto.profilePicture = this.selectedImageBase64; } - this.http.put(`${environment.apiUrls.updateUser}/${userId}`, dto) - .subscribe({ + this.http.put(`${environment.apiUrls.updateUser}/${userId}`, dto).subscribe({ + next: () => { + this.profileService.loadProfile(userId); + this.router.navigate(['/viewProfile']); + }, + error: (err) => { + console.error(err); + }, + }); + } + + onSkillSelected(event: Event): void { + const selectElement = event.target as HTMLSelectElement; + const selectedId = selectElement.value; + + if (!selectedId) { + return; + } + + const skillObj = this.availableSkills.find((s) => s.id === selectedId); + + if (skillObj && !this.userSkills.some((s) => s.id === skillObj.id)) { + this.userSkills.push({ id: skillObj.id, name: skillObj.name }); + this.pendingSkillAdditions.add(skillObj.id); + } + this.cdr.detectChanges(); + } + + removeSkill(index: number, skillId: string): void { + if (this.pendingSkillAdditions.has(skillId)) { + this.userSkills.splice(index, 1); + this.pendingSkillAdditions.delete(skillId); + } else { + const userId = this.authService.getUserId(); + if (!userId) return; + + this.profileService.removeSkillFromUser(userId, skillId).subscribe({ next: () => { - this.profileService.loadProfile(userId); - this.router.navigate(['/viewProfile']); + this.userSkills.splice(index, 1); + console.log('Skill removed successfully'); }, - error: (err) => { - console.error(err); - } + error: (err) => console.error(`Failed to remove skill ${skillId}`, err), }); + } + this.cdr.detectChanges(); } + saveSkills(): void { + const userId = this.authService.getUserId(); + console.log(this.pendingSkillAdditions); + if (!userId || this.pendingSkillAdditions.size === 0) return; + const newSkillsArray = Array.from(this.pendingSkillAdditions); -} + console.log('SENDING NEW SKILLS:', newSkillsArray); + + this.profileService.addSkillsToUser(userId, newSkillsArray).subscribe({ + next: () => { + console.log('All new skills saved'); + this.pendingSkillAdditions.clear(); + this.profileService.loadProfile(userId); + }, + error: (err) => console.error(`Failed to assign skills`, err), + }); + this.cdr.detectChanges(); + } + getAllSkills(): void { + this.skillService.skills$.subscribe({ + next: (skills) => { + this.availableSkills = skills; + console.log('skills', skills); + }, + }); + + if (this.availableSkills.length === 0) { + this.skillService.getAllSkills().subscribe({ + error: (err) => console.error('Failed to load skills list', err), + }); + } + } + + saveEducation(): void { + const userId = this.authService.getUserId(); + if (!userId) return; + + const dto: EducationCreateDto = { + school: this.educationForm.value.school, + degree: this.educationForm.value.degree, + fieldOfStudy: this.educationForm.value.fieldOfStudy, + startDate: this.educationForm.value.startDate, + endDate: this.educationForm.value.endDate || null, + description: this.educationForm.value.description, + }; + + this.educationService.createEducation(userId, dto).subscribe({ + next: () => { + this.showEducationModal = false; + this.loadEducations(); + }, + error: (err) => { + console.error('Backend returned an error:', err); + if (err.error && err.error.message) { + alert(`Error: ${err.error.message}`); + if (err.error.details) { + console.error('Backend Details:', err.error.details); + } + } else { + alert('Something went wrong on the server. Check the IDE terminal.'); + } + }, + }); + this.cdr.detectChanges(); + } + + saveExperience(): void { + const userId = this.authService.getUserId(); + if (!userId || this.experienceForm.invalid) return; + + const dto: ExperienceCreateDto = { + jobTitle: this.experienceForm.value.jobTitle, + companyName: this.experienceForm.value.companyName, + startDate: this.experienceForm.value.startDate, + endDate: this.experienceForm.value.endDate || '', + }; + + this.experienceService.createExperience(userId, dto).subscribe({ + next: () => { + this.showExperienceModal = false; + this.loadExperiences(); + this.experienceForm.reset(); + }, + error: (err) => console.error('Failed to create experience', err), + }); + this.cdr.detectChanges(); + } + + loadEducations(): void { + const userId = this.authService.getUserId(); + if (!userId) return; + + this.educationService.getEducationsByUserId(userId).subscribe({ + next: (items) => { + this.educations = items ? [...items] : []; + this.cdr.detectChanges(); + }, + error: (err) => { + console.error('Failed to load educations', err); + }, + }); + } + + loadExperiences(): void { + const userId = this.authService.getUserId(); + if (!userId) return; + + this.experienceService.getExperiencesByUserId(userId).subscribe({ + next: (items) => { + this.experiences = items ? [...items] : []; + this.cdr.detectChanges(); + }, + error: (err) => { + console.error('Failed to load experiences', err); + }, + }); + } + + deleteEducation(id: string): void { + if (!id) return; + if (!confirm('Delete this education entry?')) return; + const userId = this.authService.getUserId(); + if (!userId) return; + this.educationService.deleteEducation(userId, id).subscribe({ + next: () => { + this.loadEducations(); + this.profileService.loadProfile(userId); + }, + error: (err) => console.error('Failed to delete education', err), + }); + } + + deleteExperience(id: string): void { + if (!id) return; + if (!confirm('Delete this experience entry?')) return; + const userId = this.authService.getUserId(); + if (!userId) return; + this.experienceService.deleteExperience(userId, id).subscribe({ + next: () => { + this.loadExperiences(); + this.profileService.loadProfile(userId); + }, + error: (err) => console.error('Failed to delete experience', err), + }); + } +} diff --git a/web/src/app/components/full-job-view/full-job-view.html b/web/src/app/components/full-job-view/full-job-view.html new file mode 100644 index 0000000..6d7df13 --- /dev/null +++ b/web/src/app/components/full-job-view/full-job-view.html @@ -0,0 +1,57 @@ +
+
+ + + @if (loading) { +
+
+
+ } @else if (job) { +
+
+
+

{{job.title}}

+ +
+ +
+
+
+
+
+
+
+
+
+
+ + {{ job.employmentType }} + + @for (tag of job.tags; track $index) { + + {{ tag }} + + } +
+
+ {{ job.location }} + {{ getTimeAgo(job.createdAt) }} +
+
+ + +
+
+ } @else { +
+

Job not found

+
+ } +
+
\ No newline at end of file diff --git a/web/src/app/components/full-job-view/full-job-view.scss b/web/src/app/components/full-job-view/full-job-view.scss new file mode 100644 index 0000000..ae3aaa0 --- /dev/null +++ b/web/src/app/components/full-job-view/full-job-view.scss @@ -0,0 +1,3 @@ +.color { + color: #003049 +} \ No newline at end of file diff --git a/web/src/app/components/full-job-view/full-job-view.spec.ts b/web/src/app/components/full-job-view/full-job-view.spec.ts new file mode 100644 index 0000000..50da772 --- /dev/null +++ b/web/src/app/components/full-job-view/full-job-view.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FullJobView } from './full-job-view'; + +describe('FullJobView', () => { + let component: FullJobView; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [FullJobView], + }).compileComponents(); + + fixture = TestBed.createComponent(FullJobView); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/web/src/app/components/full-job-view/full-job-view.ts b/web/src/app/components/full-job-view/full-job-view.ts new file mode 100644 index 0000000..c8c375f --- /dev/null +++ b/web/src/app/components/full-job-view/full-job-view.ts @@ -0,0 +1,74 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { JobService } from '../../services/job-service'; +import { JobViewDto } from '../../Models/Dtos/Job/JobView-dto'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; +import { of } from 'rxjs'; +import { catchError, finalize } from 'rxjs/operators'; + +@Component({ + selector: 'app-full-job-view', + standalone: false, + templateUrl: './full-job-view.html', + styleUrl: './full-job-view.scss', +}) +export class FullJobView implements OnInit { + job: JobViewDto | null = null; + parsedMarkdown: SafeHtml = ''; + loading = true; + + constructor( + private route: ActivatedRoute, + private router: Router, + private jobService: JobService, + private sanitizer: DomSanitizer + ) {} + + ngOnInit(): void { + this.route.paramMap.subscribe(params => { + this.loading = true; + const jobId = params.get('id'); + if (!jobId) { + this.job = null; + this.parsedMarkdown = ''; + this.loading = false; + return; + } + + const cached = this.jobService.jobs.value.find(j => j.id === jobId); + if (cached) { + this.job = cached; + this.parsedMarkdown = this.sanitizer.bypassSecurityTrustHtml(this.job.description || ''); + this.loading = false; + return; + } + + this.jobService.getJobById(jobId).pipe( + catchError(err => { + console.error('getJobById error', err); + return of(null); + }), + finalize(() => { + this.loading = false; + }) + ).subscribe(res => { + if (res) { + this.job = res; + this.parsedMarkdown = this.sanitizer.bypassSecurityTrustHtml(this.job.description || ''); + } else { + this.job = null; + this.parsedMarkdown = ''; + } + }); + }); +} + + getTimeAgo(dateStr: string): string { + const defaultDateStr = dateStr.includes('Z') ? dateStr : `${dateStr.replace(' ', 'T')}Z`; + const diff = Math.floor((new Date().getTime() - new Date(defaultDateStr).getTime()) / 1000); + if (diff < 60) return `Just now`; + if (diff < 3600) return `${Math.floor(diff / 60)} minutes ago`; + if (diff < 86400) return `${Math.floor(diff / 3600)} hours ago`; + return `${Math.floor(diff / 86400)} days ago`; + } +} \ No newline at end of file diff --git a/web/src/app/components/job-detail/job-detail.html b/web/src/app/components/job-detail/job-detail.html index 616ad2e..25502b5 100644 --- a/web/src/app/components/job-detail/job-detail.html +++ b/web/src/app/components/job-detail/job-detail.html @@ -1,6 +1,6 @@
- ← Back to jobs + ← Back to jobs
@@ -9,12 +9,13 @@

{{ errorMessage }}

- Back to Home + Back to Home
-
- + +
+

{{ job.title }}

@@ -24,7 +25,7 @@

{{ job.title }}

- {{ getEmploymentTypeLabel(job.EmploymentType) }} + {{ getEmploymentTypeLabel(job.employmentType) }} @@ -42,8 +43,13 @@

{{ job.title }}

-
-
+ \ No newline at end of file diff --git a/web/src/app/components/job-detail/job-detail.ts b/web/src/app/components/job-detail/job-detail.ts index c6bc39f..c00eabc 100644 --- a/web/src/app/components/job-detail/job-detail.ts +++ b/web/src/app/components/job-detail/job-detail.ts @@ -1,11 +1,12 @@ import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { Subscription } from 'rxjs'; +import { Subscription, take } from 'rxjs'; import { JobViewDto } from '../../Models/Dtos/Job/JobView-dto'; import { EmploymentType } from '../../Models/Enums/EmploymentType'; import { AuthService } from '../../services/auth-service'; import { JobService } from '../../services/job-service'; import { ModalService } from '../../services/modal-service'; +import { ProfileService } from '../../services/profile-service'; @Component({ selector: 'app-job-detail', @@ -17,9 +18,12 @@ export class JobDetail implements OnInit, OnDestroy { job: JobViewDto | null = null; isLoading = true; errorMessage: string | null = null; + isSaved = false; + hasApplied = false; private routeSubscription: Subscription | null = null; private loadSubscription: Subscription | null = null; + private profileSub: Subscription | null = null; constructor( private route: ActivatedRoute, @@ -27,6 +31,7 @@ export class JobDetail implements OnInit, OnDestroy { private jobService: JobService, private modalService: ModalService, private authService: AuthService, + private profileService: ProfileService, private ngZone: NgZone, private cdr: ChangeDetectorRef, ) {} @@ -34,27 +39,99 @@ export class JobDetail implements OnInit, OnDestroy { ngOnInit(): void { this.routeSubscription = this.route.paramMap.subscribe((params) => { const id = params.get('id'); - if (!id) { - this.ngZone.run(() => { - this.isLoading = false; - this.job = null; - this.errorMessage = 'Invalid job id.'; - this.cdr.detectChanges(); - }); + this.handleError('Invalid job id.'); return; } - this.loadJob(id); }); + + this.profileSub = this.profileService.currentProfile$.subscribe((profile) => { + this.updateUIStatus(profile); + this.profileService.loadProfile(profile!.id!) + }); + this.checkUserJobStatus() + } + + private updateUIStatus(profile: any | null): void { + if (profile && this.job) { + this.isSaved = profile.savedJobIds?.includes(this.job.id) || false; + + const appliedIds = profile.appliedJobIds || []; + this.hasApplied = appliedIds.includes(this.job.id); + + this.cdr.detectChanges(); + } + } + + private handleError(msg: string): void { + this.ngZone.run(() => { + this.isLoading = false; + this.job = null; + this.errorMessage = msg; + this.cdr.detectChanges(); + }); } ngOnDestroy(): void { this.routeSubscription?.unsubscribe(); this.loadSubscription?.unsubscribe(); + this.profileSub?.unsubscribe(); + } + + checkUserJobStatus(): void { + if (!this.job) return; + + this.profileService.currentProfile$.pipe(take(1)).subscribe((profile) => { + if (profile) { + if (profile.savedJobIds) { + this.isSaved = profile.savedJobIds.includes(this.job!.id); + } + + if ((profile as any).appliedJobIds) { + this.hasApplied = (profile as any).appliedJobIds.includes(this.job!.id); + } + + this.cdr.detectChanges(); + } + }); + } + + toggleSave(): void { + if (!this.job) return; + + if (!this.authService.isLoggedIn()) { + this.modalService.open({ + type: 'warning', + message: 'Please log in to save jobs.', + }); + return; + } + + const originalState = this.isSaved; + this.isSaved = !this.isSaved; + + this.profileService.toggleSavedJob(this.job.id).subscribe({ + next: () => {}, + error: () => { + this.isSaved = originalState; + this.modalService.open({ + type: 'error', + message: 'Failed to update saved jobs.', + }); + }, + }); } applyNow(): void { + if (this.hasApplied) { + this.modalService.open({ + type: 'info', + message: 'You have already applied for this job.', + }); + return; + } + if (!this.authService.isLoggedIn()) { this.modalService.open({ type: 'warning', @@ -64,10 +141,7 @@ export class JobDetail implements OnInit, OnDestroy { } const roles = this.authService.getRoles().map((r) => r.toLowerCase()); - const isEmployer = roles.includes('employer'); - const isAdmin = roles.includes('admin'); - - if (isEmployer || isAdmin) { + if (roles.includes('employer') || roles.includes('admin')) { this.modalService.open({ type: 'warning', message: 'Only Job Seeker accounts can submit an application.', @@ -75,11 +149,35 @@ export class JobDetail implements OnInit, OnDestroy { return; } - if (!this.job?.id) { - return; - } + if (!this.job?.id) return; + + const hasTest = this.job.assessments && this.job.assessments.length > 0; - this.router.navigate(['/job', this.job.id, 'test']); + if (hasTest) { + this.router.navigate(['/job', this.job.id, 'test']); + } else { + this.profileService.applyToJob(this.job.id).subscribe({ + next: () => { + this.modalService.open({ + type: 'success', + message: 'Application submitted successfully!', + autoClose: true, + }); + + const userId = this.authService.getUserId(); + if (userId) { + this.profileService.loadProfile(userId); + } + this.hasApplied = true + }, + error: () => { + this.modalService.open({ + type: 'error', + message: 'Failed to submit application.', + }); + }, + }); + } } shareJob(): void { @@ -112,7 +210,9 @@ export class JobDetail implements OnInit, OnDestroy { } getTimeAgo(dateStr: string): string { - const date = new Date(dateStr); + const formattedDateStr = + dateStr.includes('Z') || dateStr.includes('+') ? dateStr : `${dateStr.replace(' ', 'T')}Z`; + const date = new Date(formattedDateStr); const time = date.getTime(); if (!dateStr || Number.isNaN(time)) { @@ -142,6 +242,9 @@ export class JobDetail implements OnInit, OnDestroy { } getEmploymentTypeLabel(type: EmploymentType | null | undefined): string { + const name = type === undefined || null ? 'Unknown' : type?.toString() + return name as string + /* switch (type) { case EmploymentType.FullTime: return 'Full-time'; @@ -154,6 +257,7 @@ export class JobDetail implements OnInit, OnDestroy { default: return 'Unknown'; } + */ } private loadJob(id: string): void { @@ -162,23 +266,53 @@ export class JobDetail implements OnInit, OnDestroy { this.errorMessage = null; this.job = null; - this.loadSubscription = this.jobService - .getJobById(id) - .subscribe({ - next: (job) => { - this.ngZone.run(() => { - this.job = job; - this.isLoading = false; - this.cdr.detectChanges(); - }); - }, - error: () => { - this.ngZone.run(() => { - this.errorMessage = 'Job not found or unavailable.'; - this.isLoading = false; - this.cdr.detectChanges(); - }); - }, - }); + this.loadSubscription = this.jobService.getJobById(id).subscribe({ + next: (job) => { + this.ngZone.run(() => { + this.job = this.parseJobTags(job); + this.checkUserJobStatus(); + this.isLoading = false; + this.cdr.detectChanges(); + }); + }, + error: () => { + this.ngZone.run(() => { + this.errorMessage = 'Job not found or unavailable.'; + this.isLoading = false; + this.cdr.detectChanges(); + }); + }, + }); + } + + private parseJobTags(job: JobViewDto): JobViewDto { + const parsedJob = { ...job } + + if (typeof parsedJob.tags === 'string') { + const tagString = parsedJob.tags as unknown as string + + parsedJob.tags = tagString + .split(',') + .map((t) => + t + .trim() + .replace(/[\[\]"']/g, '') + ) + .filter((t) => t !== '') + } + + if (Array.isArray(parsedJob.tags)) { + parsedJob.tags = parsedJob.tags.map((t) => + typeof t === 'string' + ? t.replace(/[\[\]"']/g, '').trim() + : t + ); + } + + if (!Array.isArray(parsedJob.tags)) { + parsedJob.tags = []; + } + + return parsedJob } } diff --git a/web/src/app/components/job-edit/job-edit.html b/web/src/app/components/job-edit/job-edit.html index ce47744..4b15920 100644 --- a/web/src/app/components/job-edit/job-edit.html +++ b/web/src/app/components/job-edit/job-edit.html @@ -1,6 +1,6 @@
@@ -17,7 +17,8 @@

Update job advertisement

- +
@@ -41,26 +42,34 @@

Update job advertisement

+
+ + +
+ @if(isPreView$ | async){ -
- -
-
- -
+
+ +
+
+
+
} @else { -
- - -
- -
+
+ + +
+
+
}
@@ -72,28 +81,29 @@

Update job advertisement

@if (selectedAssessments.length > 0) { -
- @for (assessment of selectedAssessments; track assessment.id) { -
-
-
{{ assessment.title }}
- Difficulty: {{ assessment.difficultyLevel }} • {{ assessment.questions?.length || 0 }} questions -
- -
- } +
+ @for (assessment of selectedAssessments; track assessment.id) { +
+
+
{{ assessment.title }}
+ Difficulty: {{ assessment.difficultyLevel }} • {{ assessment.questions?.length + || 0 }} questions +
+
+ } +
} @else { -

No tests selected yet. Add a test template to evaluate your applicants.

+

No tests selected yet. Add a test template to evaluate your applicants.

}
@if (error) { -
-

{{ error }}

-
+
+

{{ error }}

+
}
@if (showAssessmentModal) { - -