-
Notifications
You must be signed in to change notification settings - Fork 0
feat: data export, in-app feedback, and EN/DE localization (polish batch) #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
0388e25
feat: add per-user test export data and CSV serialization to engine
timh8127 8f54c34
feat: add self-service data export endpoint and Settings UI
timh8127 3d680c5
feat: add Feedback entity, context mapping, and migration
timh8127 24b18e5
feat: add feedback submission and listing service
timh8127 60680e4
feat: add in-app feedback page with submission form and admin listing
timh8127 53717d9
test: cover feedback submission and admin listing
timh8127 fd491e5
feat: add resx localization infrastructure and language toggle
timh8127 f19e890
feat: translate app pages (Home, Study, Settings, Feedback) to EN/DE
timh8127 7eaf7c6
feat: translate landing page and playground to EN/DE
timh8127 e9e21c2
feat: translate privacy policy and terms of use to EN/DE
timh8127 337d16d
fix: address CodeRabbit review on PR #32
timh8127 e171abe
feat: admin can change feedback status; link nav brand to home
timh8127 5e79c01
feat: let users see their own feedback submission history
timh8127 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| namespace WSIST.Engine; | ||
|
|
||
| public class Feedback | ||
| { | ||
| public int Id { get; set; } | ||
| public int UserId { get; set; } | ||
| public User? User { get; set; } | ||
| public required string Message { get; set; } | ||
| public FeedbackCategory Category { get; set; } | ||
| public FeedbackStatus Status { get; set; } = FeedbackStatus.Open; | ||
|
|
||
| // Default so a Feedback created without an explicit timestamp never persists | ||
| // DateTime.MinValue (0001-01-01) — mirrors User.CreatedAt. | ||
| public DateTime CreatedAt { get; set; } = DateTime.UtcNow; | ||
|
|
||
| public enum FeedbackCategory | ||
| { | ||
| Bug = 0, | ||
| Feature = 1, | ||
| Other = 2, | ||
| } | ||
|
|
||
| public enum FeedbackStatus | ||
| { | ||
| Open = 0, | ||
| Reviewed = 1, | ||
| Closed = 2, | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| using Microsoft.EntityFrameworkCore; | ||
|
|
||
| namespace WSIST.Engine; | ||
|
|
||
| public class FeedbackManagement | ||
| { | ||
| private readonly WsistContext context; | ||
|
|
||
| public FeedbackManagement(WsistContext context) | ||
| { | ||
| this.context = context; | ||
| } | ||
|
|
||
| public Feedback Submit(int userId, string message, Feedback.FeedbackCategory category) | ||
| { | ||
| var trimmed = message?.Trim() ?? string.Empty; | ||
| if (trimmed.Length == 0) | ||
| throw new ArgumentException("Feedback message cannot be empty.", nameof(message)); | ||
|
|
||
| // Defensively cap to the column length so an over-long message surfaces | ||
| // as a validation error rather than a DbUpdateException at SaveChanges. | ||
| if (trimmed.Length > 4000) | ||
| throw new ArgumentException( | ||
| "Feedback message is too long (4000 characters max).", | ||
| nameof(message) | ||
| ); | ||
|
|
||
| if (!Enum.IsDefined(category)) | ||
| throw new ArgumentException("Unknown feedback category.", nameof(category)); | ||
|
|
||
| var feedback = new Feedback | ||
| { | ||
| UserId = userId, | ||
| Message = trimmed, | ||
| Category = category, | ||
| Status = Feedback.FeedbackStatus.Open, | ||
| CreatedAt = DateTime.UtcNow, | ||
| }; | ||
| context.Feedbacks.Add(feedback); | ||
| context.SaveChanges(); | ||
| return feedback; | ||
| } | ||
|
|
||
| // A user's own submissions (newest first) for their feedback history. | ||
| public List<FeedbackView> GetForUser(int userId) | ||
| { | ||
| return context | ||
| .Feedbacks.AsNoTracking() | ||
| .Where(f => f.UserId == userId) | ||
| .OrderByDescending(f => f.CreatedAt) | ||
| .ThenByDescending(f => f.Id) | ||
| .Select(f => new FeedbackView( | ||
| f.Id, | ||
| f.Message, | ||
| f.Category, | ||
| f.Status, | ||
| f.CreatedAt, | ||
| f.User != null ? f.User.DisplayName : "(unknown)", | ||
| f.User != null ? f.User.Email : "" | ||
| )) | ||
| .ToList(); | ||
| } | ||
|
|
||
| // Admin-only listing. Authorization (is the caller the owner?) is enforced | ||
| // by the page that calls this — the engine intentionally has no notion of | ||
| // who the admin is, so it stays config-driven in the web layer. | ||
| public List<FeedbackView> GetAll() | ||
| { | ||
| return context | ||
| .Feedbacks.AsNoTracking() | ||
| .OrderByDescending(f => f.CreatedAt) | ||
| // Tie-break on Id so equal timestamps order stably. | ||
| .ThenByDescending(f => f.Id) | ||
| .Select(f => new FeedbackView( | ||
| f.Id, | ||
| f.Message, | ||
| f.Category, | ||
| f.Status, | ||
| f.CreatedAt, | ||
| f.User != null ? f.User.DisplayName : "(unknown)", | ||
| f.User != null ? f.User.Email : "" | ||
| )) | ||
| .ToList(); | ||
| } | ||
|
|
||
| // Update a submission's workflow status (Open/Reviewed/Closed). Admin-only; | ||
| // the calling page enforces that. Returns false if the row no longer exists. | ||
| public bool UpdateStatus(int feedbackId, Feedback.FeedbackStatus status) | ||
| { | ||
| if (!Enum.IsDefined(status)) | ||
| throw new ArgumentException("Unknown feedback status.", nameof(status)); | ||
|
|
||
| var feedback = context.Feedbacks.Find(feedbackId); | ||
| if (feedback is null) | ||
| return false; | ||
| feedback.Status = status; | ||
| context.SaveChanges(); | ||
| return true; | ||
| } | ||
|
|
||
| public record FeedbackView( | ||
| int Id, | ||
| string Message, | ||
| Feedback.FeedbackCategory Category, | ||
| Feedback.FeedbackStatus Status, | ||
| DateTime CreatedAt, | ||
| string SubmittedByName, | ||
| string SubmittedByEmail | ||
| ); | ||
| } | ||
246 changes: 246 additions & 0 deletions
246
WSIST/WSIST.Engine/Migrations/20260617062848_AddFeedbackTable.Designer.cs
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.