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
29 changes: 29 additions & 0 deletions WSIST/WSIST.Engine/Feedback.cs
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,
}
}
110 changes: 110 additions & 0 deletions WSIST/WSIST.Engine/FeedbackManagement.cs
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(
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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
);
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading