Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions JustBigO(Fun).Tests/Controllers/HomeControllerTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using JustBigO_Fun_.Controllers;
using JustBigO_Fun_.Data;
using JustBigO_Fun_.Models;
using JustBigO_Fun_.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -33,7 +34,7 @@ public async Task Index_ReturnsViewWithProblems()
db.SaveChanges();

var mockLogger = new Mock<ILogger<HomeController>>();
var controller = new HomeController(mockLogger.Object, db);
var controller = new HomeController(mockLogger.Object, db, new MarkdownRenderer(new HtmlSanitizerService()));

// Mocking User identity
var user = new System.Security.Claims.ClaimsPrincipal(new System.Security.Claims.ClaimsIdentity());
Expand Down Expand Up @@ -61,7 +62,7 @@ public async Task Index_FiltersByDifficulty()
db.SaveChanges();

var mockLogger = new Mock<ILogger<HomeController>>();
var controller = new HomeController(mockLogger.Object, db);
var controller = new HomeController(mockLogger.Object, db, new MarkdownRenderer(new HtmlSanitizerService()));

// Mocking User identity
var user = new System.Security.Claims.ClaimsPrincipal(new System.Security.Claims.ClaimsIdentity());
Expand All @@ -86,7 +87,7 @@ public async Task Solve_ReturnsNotFound_WhenProblemDoesNotExist()
// Arrange
using var db = new ApplicationDbContext(_options);
var mockLogger = new Mock<ILogger<HomeController>>();
var controller = new HomeController(mockLogger.Object, db);
var controller = new HomeController(mockLogger.Object, db, new MarkdownRenderer(new HtmlSanitizerService()));

// Act
var result = await controller.Solve(999);
Expand Down
58 changes: 58 additions & 0 deletions JustBigO(Fun)/Areas/Admin/Views/Dashboard/Index.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
@model AdminDashboardVm
@{
ViewData["Title"] = "Admin dashboard";
}

<div class="container py-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="text-white mb-1">Admin dashboard</h1>
<p class="text-muted mb-0">Manage problems, test cases and user access.</p>
</div>
<a asp-controller="Home" asp-action="Index" asp-area="" class="btn jbo-btn-ghost">← Back to site</a>
</div>

<div class="row g-3 mb-4">
<div class="col-6 col-md-3">
<div class="jbo-section-card text-center py-3">
<div class="text-white fs-3 fw-bold">@Model.ProblemCount</div>
<div class="text-muted small">Problems</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="jbo-section-card text-center py-3">
<div class="text-white fs-3 fw-bold">@Model.TestCount</div>
<div class="text-muted small">Test cases</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="jbo-section-card text-center py-3">
<div class="text-white fs-3 fw-bold">@Model.SubmissionCount</div>
<div class="text-muted small">Submissions</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="jbo-section-card text-center py-3">
<div class="text-white fs-3 fw-bold">@Model.UserCount</div>
<div class="text-muted small">Users (@Model.AdminCount admin@(Model.AdminCount == 1 ? "" : "s"))</div>
</div>
</div>
</div>

<div class="row g-3">
<div class="col-md-6">
<div class="jbo-section-card p-4 h-100 d-flex flex-column">
<h5 class="text-white">Problems</h5>
<p class="text-muted flex-grow-1">Create, edit and delete problems, manage code templates and test cases.</p>
<a asp-controller="Problems" asp-action="Index" class="btn jbo-btn-primary align-self-start">Manage problems →</a>
</div>
</div>
<div class="col-md-6">
<div class="jbo-section-card p-4 h-100 d-flex flex-column">
<h5 class="text-white">Users</h5>
<p class="text-muted flex-grow-1">View registered users and promote or demote their roles.</p>
<a asp-controller="Users" asp-action="Index" class="btn jbo-btn-primary align-self-start">Manage users →</a>
</div>
</div>
</div>
</div>
48 changes: 32 additions & 16 deletions JustBigO(Fun)/Areas/Admin/Views/Problems/Create.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
}

<div class="container py-4">
<h1 class="text-white mb-4">New problem</h1>
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="text-white mb-0">New problem</h1>
<a asp-action="Index" class="btn jbo-btn-ghost">← Back to problems</a>
</div>

<form asp-action="Create" method="post" enctype="multipart/form-data" class="jbo-section-card p-4">
<div asp-validation-summary="All" class="text-danger mb-3"></div>
Expand All @@ -18,35 +21,29 @@
<label asp-for="Slug" class="form-label text-white">Slug (URL)</label>
<input asp-for="Slug" class="form-control bg-dark text-white border-secondary" placeholder="ex: two-sum" />
</div>
<div class="col-md-4">
<div class="col-md-6">
<label asp-for="Difficulty" class="form-label text-white">Difficulty</label>
<select asp-for="Difficulty" class="form-select bg-dark text-white border-secondary">
<option value="Easy">Easy</option>
<option value="Medium">Medium</option>
<option value="Hard">Hard</option>
</select>
</div>
<div class="col-md-4">
<label asp-for="OrderIndex" class="form-label text-white">Order</label>
<input asp-for="OrderIndex" type="number" class="form-control bg-dark text-white border-secondary" />
</div>
<div class="col-md-4">
<div class="col-md-6">
<label asp-for="Tags" class="form-label text-white">Tags (comma-separated)</label>
<input asp-for="Tags" class="form-control bg-dark text-white border-secondary" placeholder="Array, Hash Map" />
</div>

<div class="col-12">
<label asp-for="Description" class="form-label text-white">Description (HTML)</label>
<textarea asp-for="Description" class="form-control bg-dark text-white border-secondary" rows="6" placeholder="<p>Problem statement...</p>"></textarea>
</div>
<div class="col-12">
<label class="form-label text-white">Code templates (JSON)</label>
<textarea asp-for="CodeTemplatesJson" class="form-control bg-dark text-white border-secondary font-monospace" rows="8" style="font-size: 0.85rem;"></textarea>
<small class="text-muted">Ex: {"python":"def solve():...","java":"class Solution{...}","cpp":"..."}</small>
<label asp-for="Description" class="form-label text-white">Description</label>
<textarea asp-for="Description" class="form-control" rows="8"></textarea>
</div>

<partial name="_CodeTemplatesEditor" model="Model" />

<div class="col-12 border-top border-secondary pt-4 mt-3">
<h5 class="text-white mb-3">Test files (.in / .out) – hidden from users</h5>
<p class="text-muted small mb-2">File content is stored in the database (not the files themselves). For multiple tests: select ALL .in files in the first field and ALL .out files in the second (Ctrl+Click). Pairing: 1st .in ↔ 1st .out, 2nd ↔ 2nd, etc.</p>
<h5 class="text-white mb-3">Test files (.in / .out)</h5>
<p class="text-muted small mb-2">Select ALL .in files in the first field and ALL .out files in the second (Ctrl+Click). Pairing: 1st .in ↔ 1st .out, 2nd ↔ 2nd, etc.</p>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label text-white">.in files</label>
Expand All @@ -59,10 +56,29 @@
</div>
</div>

<div class="col-12 border-top border-secondary pt-4 mt-3">
<h6 class="text-white mb-3">Add a test case</h6>
<div class="row g-3">
<div class="col-md-6">
<label asp-for="FirstTestInput" class="form-label text-white small">Input</label>
<textarea asp-for="FirstTestInput" class="form-control bg-dark text-white border-secondary font-monospace" rows="4" style="font-size: 0.85rem;" spellcheck="false"></textarea>
</div>
<div class="col-md-6">
<label asp-for="FirstTestExpectedOutput" class="form-label text-white small">Expected output</label>
<textarea asp-for="FirstTestExpectedOutput" class="form-control bg-dark text-white border-secondary font-monospace" rows="4" style="font-size: 0.85rem;" spellcheck="false"></textarea>
</div>
</div>
</div>

<div class="col-12 d-flex gap-2 pt-3">
<button type="submit" class="btn jbo-btn-primary">Save</button>
<a asp-action="Index" class="btn jbo-btn-ghost">Cancel</a>
</div>
</div>
</form>
</div>

@section Scripts {
<partial name="_MarkdownEditorAssets" />
<partial name="_CodeTemplatesScripts" />
}
96 changes: 85 additions & 11 deletions JustBigO(Fun)/Areas/Admin/Views/Problems/Edit.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,19 @@
}

<div class="container py-4">
<h1 class="text-white mb-4">Edit: @Model.Title</h1>
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="text-white mb-0">Edit: @Model.Title</h1>
<a asp-action="Index" class="btn jbo-btn-ghost">← Back to problems</a>
</div>

@if (TempData["AdminMessage"] is string okMsg)
{
<div class="alert alert-success">@okMsg</div>
}
@if (TempData["AdminError"] is string errMsg)
{
<div class="alert alert-danger">@errMsg</div>
}

<form asp-action="Edit" asp-route-id="@Model.Id" method="post" enctype="multipart/form-data" class="jbo-section-card p-4">
<input type="hidden" asp-for="Id" />
Expand All @@ -28,25 +40,23 @@
</select>
</div>
<div class="col-md-4">
<label asp-for="OrderIndex" class="form-label text-white">Order</label>
<input asp-for="OrderIndex" type="number" class="form-control bg-dark text-white border-secondary" />
<label class="form-label text-white">Order (auto-assigned)</label>
<input value="@Model.OrderIndex" class="form-control bg-dark text-muted border-secondary" readonly disabled />
</div>
<div class="col-md-4">
<label asp-for="Tags" class="form-label text-white">Tags (comma-separated)</label>
<input asp-for="Tags" class="form-control bg-dark text-white border-secondary" />
</div>
<div class="col-12">
<label asp-for="Description" class="form-label text-white">Description (HTML)</label>
<textarea asp-for="Description" class="form-control bg-dark text-white border-secondary" rows="6"></textarea>
</div>
<div class="col-12">
<label asp-for="CodeTemplatesJson" class="form-label text-white">Code templates (JSON)</label>
<textarea asp-for="CodeTemplatesJson" class="form-control bg-dark text-white border-secondary font-monospace" rows="8" style="font-size: 0.85rem;"></textarea>
<label asp-for="Description" class="form-label text-white">Description</label>
<textarea asp-for="Description" class="form-control" rows="8"></textarea>
</div>

<partial name="_CodeTemplatesEditor" model="Model" />

<div class="col-12 border-top border-secondary pt-4 mt-3">
<h5 class="text-white mb-3">Add new tests (.in / .out)</h5>
<p class="text-muted small mb-2">@Model.ExistingTestCount existing tests. To add more: select ALL new .in files and ALL new .out files (Ctrl+Click). Pairing: 1st .in ↔ 1st .out, 2nd ↔ 2nd, etc.</p>
<h5 class="text-white mb-3">Bulk add tests from files (.in / .out)</h5>
<p class="text-muted small mb-2">Select ALL new .in files and ALL new .out files (Ctrl+Click). Pairing: 1st .in ↔ 1st .out, 2nd ↔ 2nd, etc. Uploaded tests are appended to the list below after saving.</p>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label text-white">.in files</label>
Expand All @@ -65,4 +75,68 @@
</div>
</div>
</form>

@* Test case management lives outside the main form because HTML forms cannot be nested. *@
<div class="jbo-section-card p-4 mt-4">
<h5 class="text-white mb-3">Test cases (@Model.Tests.Count)</h5>

@if (Model.Tests.Count == 0)
{
<p class="text-muted">No test cases yet. Add one below.</p>
}
else
{
<div class="d-flex flex-column gap-3 mb-4">
@foreach (var t in Model.Tests)
{
<div class="border border-secondary rounded p-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="text-white fw-bold">Test #@t.OrderIndex</span>
<form asp-action="DeleteTest" asp-route-problemId="@Model.Id" asp-route-testId="@t.Id" method="post" class="m-0" onsubmit="return confirm('Delete this test case?');">
@Html.AntiForgeryToken()
<button type="submit" class="btn btn-sm btn-outline-danger">Delete</button>
</form>
</div>
<form asp-action="EditTest" asp-route-problemId="@Model.Id" asp-route-testId="@t.Id" method="post">
@Html.AntiForgeryToken()
<div class="row g-3">
<div class="col-md-6">
<label class="form-label text-white small">Input</label>
<textarea name="inputJson" class="form-control bg-dark text-white border-secondary font-monospace" rows="4" style="font-size: 0.85rem;" spellcheck="false">@t.InputJson</textarea>
</div>
<div class="col-md-6">
<label class="form-label text-white small">Expected output</label>
<textarea name="expectedOutputJson" class="form-control bg-dark text-white border-secondary font-monospace" rows="4" style="font-size: 0.85rem;" spellcheck="false">@t.ExpectedOutputJson</textarea>
</div>
</div>
<button type="submit" class="btn btn-sm jbo-btn-primary mt-2">Save test</button>
</form>
</div>
}
</div>
}

<div class="border-top border-secondary pt-3">
<h6 class="text-white mb-3">Add a test case</h6>
<form asp-action="AddTest" asp-route-problemId="@Model.Id" method="post">
@Html.AntiForgeryToken()
<div class="row g-3">
<div class="col-md-6">
<label class="form-label text-white small">Input</label>
<textarea name="inputJson" class="form-control bg-dark text-white border-secondary font-monospace" rows="4" style="font-size: 0.85rem;" spellcheck="false"></textarea>
</div>
<div class="col-md-6">
<label class="form-label text-white small">Expected output</label>
<textarea name="expectedOutputJson" class="form-control bg-dark text-white border-secondary font-monospace" rows="4" style="font-size: 0.85rem;" spellcheck="false"></textarea>
</div>
</div>
<button type="submit" class="btn btn-sm jbo-btn-primary mt-2">+ Add test</button>
</form>
</div>
</div>
</div>

@section Scripts {
<partial name="_MarkdownEditorAssets" />
<partial name="_CodeTemplatesScripts" />
}
5 changes: 2 additions & 3 deletions JustBigO(Fun)/Areas/Admin/Views/Problems/Index.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,10 @@
</tr>
</thead>
<tbody>
@{ var i = 1; }
@foreach (var p in Model ?? Enumerable.Empty<ProblemListVm>())
{
<tr>
<td>@(i++)</td>
<td>@p.OrderIndex</td>
<td>@p.Title</td>
<td><code>@p.Slug</code></td>
<td>@p.Difficulty</td>
Expand All @@ -45,6 +44,6 @@
</div>

<p class="mt-3">
<a asp-controller="Home" asp-action="Index" asp-area="" class="jbo-problem-link">← Back to Dashboard</a>
<a asp-controller="Dashboard" asp-action="Index" class="jbo-problem-link">← Back to admin dashboard</a>
</p>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
@model ProblemEditVm
@{
// Unique suffix so multiple instances / future reuse don't collide on element ids.
var tabId = "tmpl-" + (Model.Id == 0 ? "new" : Model.Id.ToString());
}

<div class="col-12 border-top border-secondary pt-4 mt-3">
<h5 class="text-white mb-3">Code templates</h5>

<ul class="nav nav-tabs jbo-template-tabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#@(tabId)-python" type="button" role="tab">Python</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#@(tabId)-java" type="button" role="tab">Java</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#@(tabId)-cpp" type="button" role="tab">C++</button>
</li>
</ul>

<div class="tab-content border border-top-0 border-secondary p-3">
<div class="tab-pane fade show active" id="@(tabId)-python" role="tabpanel">
<textarea asp-for="PythonTemplate" class="d-none" spellcheck="false"></textarea>
<div id="monaco-python" class="jbo-template-editor" data-lang="python"></div>
</div>
<div class="tab-pane fade" id="@(tabId)-java" role="tabpanel">
<textarea asp-for="JavaTemplate" class="d-none" spellcheck="false"></textarea>
<div id="monaco-java" class="jbo-template-editor" data-lang="java"></div>
</div>
<div class="tab-pane fade" id="@(tabId)-cpp" role="tabpanel">
<textarea asp-for="CppTemplate" class="d-none" spellcheck="false"></textarea>
<div id="monaco-cpp" class="jbo-template-editor" data-lang="cpp"></div>
</div>
</div>
</div>
Loading
Loading