125 lines
5.3 KiB
C#
125 lines
5.3 KiB
C#
using PlotLine.Data;
|
|
using PlotLine.Models;
|
|
using PlotLine.ViewModels;
|
|
|
|
namespace PlotLine.Services;
|
|
|
|
public interface IFeatureRequestService
|
|
{
|
|
Task<FeatureRequest> CreateAsync(int userId, FeatureRequestCreateViewModel model);
|
|
Task<FeatureRequest?> GetForUserAsync(int featureRequestId, int userId);
|
|
Task<FeatureRequest?> GetForAdminAsync(int featureRequestId);
|
|
Task<IReadOnlyList<FeatureRequest>> ListForUserAsync(int userId);
|
|
Task<IReadOnlyList<FeatureRequest>> ListForAdminAsync(AdminFeatureRequestFilter filter);
|
|
Task<FeatureRequestAdminSummary> GetAdminSummaryAsync();
|
|
Task<bool> UpdateAdminFieldsAsync(AdminFeatureRequestUpdateViewModel model);
|
|
Task<bool> AddUserReplyAsync(int featureRequestId, int userId, string messageBody);
|
|
Task<bool> AddAdminReplyAsync(int featureRequestId, int adminUserId, string messageBody);
|
|
Task<IReadOnlyList<FeatureRequestMessage>> ListMessagesAsync(int featureRequestId);
|
|
}
|
|
|
|
public sealed class FeatureRequestService(IFeatureRequestRepository featureRequests) : IFeatureRequestService
|
|
{
|
|
public Task<FeatureRequest> CreateAsync(int userId, FeatureRequestCreateViewModel model)
|
|
{
|
|
return featureRequests.CreateAsync(
|
|
userId,
|
|
Clean(model.Title),
|
|
Clean(model.Description),
|
|
CleanNullable(model.AppArea),
|
|
CleanNullable(model.Importance));
|
|
}
|
|
|
|
public Task<FeatureRequest?> GetForUserAsync(int featureRequestId, int userId) =>
|
|
featureRequests.GetForUserAsync(featureRequestId, userId);
|
|
|
|
public Task<FeatureRequest?> GetForAdminAsync(int featureRequestId) =>
|
|
featureRequests.GetForAdminAsync(featureRequestId);
|
|
|
|
public Task<IReadOnlyList<FeatureRequest>> ListForUserAsync(int userId) =>
|
|
featureRequests.ListForUserAsync(userId);
|
|
|
|
public Task<IReadOnlyList<FeatureRequest>> ListForAdminAsync(AdminFeatureRequestFilter filter)
|
|
{
|
|
var sanitized = SanitizeFilter(filter);
|
|
return featureRequests.ListForAdminAsync(
|
|
sanitized.Status,
|
|
sanitized.AppArea,
|
|
sanitized.Importance,
|
|
sanitized.Search,
|
|
sanitized.Sort);
|
|
}
|
|
|
|
public async Task<FeatureRequestAdminSummary> GetAdminSummaryAsync()
|
|
{
|
|
var requests = await featureRequests.ListForAdminAsync(
|
|
status: null,
|
|
appArea: null,
|
|
importance: null,
|
|
search: null,
|
|
sort: FeatureRequestSortOptions.LatestActivity);
|
|
|
|
return new FeatureRequestAdminSummary
|
|
{
|
|
Statuses = FeatureRequestStatuses.All
|
|
.Select(status => new FeatureRequestStatusSummary
|
|
{
|
|
Status = status,
|
|
Count = requests.Count(request => string.Equals(request.Status, status, StringComparison.OrdinalIgnoreCase))
|
|
})
|
|
.ToList(),
|
|
TotalOpenRequests = requests.Count(request => request.IsOpen),
|
|
AwaitingAdminReply = requests.Count(request => request.ConversationState == FeatureRequestConversationStates.WaitingForAdminReply),
|
|
AwaitingUserReply = requests.Count(request => request.ConversationState == FeatureRequestConversationStates.WaitingForUserReply)
|
|
};
|
|
}
|
|
|
|
public async Task<bool> UpdateAdminFieldsAsync(AdminFeatureRequestUpdateViewModel model)
|
|
{
|
|
if (!FeatureRequestStatuses.IsValid(model.Status))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var updated = await featureRequests.UpdateStatusAsync(model.FeatureRequestID, model.Status, model.IsPublicCandidate);
|
|
if (updated is null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
await featureRequests.UpdateAdminNotesAsync(model.FeatureRequestID, CleanNullable(model.AdminNotes));
|
|
return true;
|
|
}
|
|
|
|
public async Task<bool> AddUserReplyAsync(int featureRequestId, int userId, string messageBody)
|
|
{
|
|
var message = await featureRequests.AddMessageAsync(featureRequestId, userId, isAdmin: false, Clean(messageBody));
|
|
return message is not null;
|
|
}
|
|
|
|
public async Task<bool> AddAdminReplyAsync(int featureRequestId, int adminUserId, string messageBody)
|
|
{
|
|
var message = await featureRequests.AddMessageAsync(featureRequestId, adminUserId, isAdmin: true, Clean(messageBody));
|
|
return message is not null;
|
|
}
|
|
|
|
public Task<IReadOnlyList<FeatureRequestMessage>> ListMessagesAsync(int featureRequestId) =>
|
|
featureRequests.ListMessagesAsync(featureRequestId);
|
|
|
|
private static string Clean(string value) => value.Trim();
|
|
|
|
private static string? CleanNullable(string? value) => string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
|
|
|
private static AdminFeatureRequestFilter SanitizeFilter(AdminFeatureRequestFilter filter)
|
|
{
|
|
return new AdminFeatureRequestFilter
|
|
{
|
|
Status = FeatureRequestStatuses.IsValid(filter.Status) ? filter.Status : null,
|
|
AppArea = FeatureRequestOptions.AppAreas.Contains(filter.AppArea ?? string.Empty, StringComparer.OrdinalIgnoreCase) ? filter.AppArea : null,
|
|
Importance = FeatureRequestOptions.ImportanceOptions.Contains(filter.Importance ?? string.Empty, StringComparer.OrdinalIgnoreCase) ? filter.Importance : null,
|
|
Search = CleanNullable(filter.Search),
|
|
Sort = FeatureRequestSortOptions.IsValid(filter.Sort) ? filter.Sort : FeatureRequestSortOptions.LatestActivity
|
|
};
|
|
}
|
|
}
|