Add certificate PDF download to Certificate page#61
Conversation
There was a problem hiding this comment.
Copilot wasn't able to review any files in this pull request.
You can also share your feedback on Copilot code review. Take the survey.
Co-authored-by: Hemavathi15sg <224925058+Hemavathi15sg@users.noreply.github.com>
| public async Task<ActionResult<IEnumerable<CertificateDto>>> SearchCertificates( | ||
| [FromQuery] string studentName = "") | ||
| { | ||
| _logger.LogInformation("Searching certificates for student name: {StudentName}", studentName); |
Check failure
Code scanning / CodeQL
Log entries created from user input High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 11 days ago
In general, to fix this kind of issue, all user-provided data that is written into log messages should be sanitized to remove or encode characters that could alter the log structure (notably \r and \n). For plain-text logs, removing newline characters is usually sufficient; for HTML logs, HTML-encoding should be used. Since this controller likely writes to plain-text/structured logs, the minimal non-breaking fix is to create a sanitized version of studentName with line breaks removed and use that sanitized variable in the log call, leaving the underlying business logic (GetCertificatesByStudentNameAsync) unchanged.
Concretely, in api/CourseRegistration.API/Controllers/CertificatesController.cs, within the SearchCertificates action, we’ll introduce a local variable (e.g., sanitizedStudentName) that replaces any \r and \n characters with empty strings (or otherwise strips them) before passing it to _logger.LogInformation. The rest of the method will continue to use the original studentName for validation and searching to avoid altering functional behavior. No new using directives or external dependencies are needed; we can use string.Replace from the BCL. The change is localized around line 59: add the sanitization line immediately before the log call and change the log call to use the sanitized variable.
| @@ -56,8 +56,13 @@ | ||
| public async Task<ActionResult<IEnumerable<CertificateDto>>> SearchCertificates( | ||
| [FromQuery] string studentName = "") | ||
| { | ||
| _logger.LogInformation("Searching certificates for student name: {StudentName}", studentName); | ||
| var sanitizedStudentName = studentName? | ||
| .Replace(Environment.NewLine, string.Empty) | ||
| .Replace("\n", string.Empty) | ||
| .Replace("\r", string.Empty); | ||
|
|
||
| _logger.LogInformation("Searching certificates for student name: {StudentName}", sanitizedStudentName); | ||
|
|
||
| if (string.IsNullOrWhiteSpace(studentName)) | ||
| { | ||
| return BadRequest(new { message = "Student name is required" }); |
The Certificate page showed only a certificate ID with no way to download or share it. This adds a
GET /api/certificates/{certificateId}/downloadendpoint and a frontend Download PDF button with full loading/error UX.Backend
ICertificateService/CertificateService— newDownloadCertificatePdfAsync(Guid)method; generates a valid PDF using pure C# (no new dependencies) with Latin1-safe escaping, 60-char name truncation, and ISO 8601 UTC timestampsCertificatesController(new) — three endpoints:GET /api/certificates/{id}— fetch by IDGET /api/certificates/search?studentName=— search (already referenced in HomeController HTML)GET /api/certificates/{certificateId}/download— streamsapplication/pdfwithContent-Disposition: attachmentProgram.cs— registersICertificateService → CertificateServiceFrontend
certificate.js— addsdownloading/downloadErrorstate;handleDownloadcalls the download endpoint via axios blob response, creates a temporary anchor to trigger browser download, and surfaces 404 vs. network errors distinctlycertificate.css(new) — button and error styles; button color (#4c51bf) meets WCAG AA contrastTests
Five new xUnit tests in
CertificateServiceDownloadTests: valid ID returns bytes,%PDF-header present, invalid/empty GUID returnsnull, PDF content contains expected student/course details.Original prompt
📍 Connect Copilot coding agent with Jira, Azure Boards or Linear to delegate work to Copilot in one click without leaving your project management tool.