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
38 changes: 32 additions & 6 deletions EliteAPI.Tests/Guides/GuideEndpointTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ await App.ModeratorClient.POSTAsync<VoteGuideEndpoint, VoteGuideRequest>(
[Fact, Priority(22)]
public async Task UpdatePublishedGuide_MaintainsVisibility()
{
// 1. Create and publish a guide
// Create and publish a guide
var (createRsp, created) = await App.RegularUserClient.POSTAsync<CreateGuideEndpoint, CreateGuideRequest, GuideDto>(
new CreateGuideRequest { Type = GuideType.General });
createRsp.IsSuccessStatusCode.ShouldBeTrue();
Expand All @@ -383,34 +383,60 @@ await App.RegularUserClient.PUTAsync<UpdateGuideEndpoint, UpdateGuideRequest>(
MarkdownContent = "# Updated"
});

// 3. Author submits update (Status: Published -> PendingApproval)
// Author submits update (Status: Published -> PendingApproval)
var submitRsp = await App.RegularUserClient.POSTAsync<SubmitGuideForApprovalEndpoint, SubmitGuideRequest>(
new SubmitGuideRequest { GuideId = created.Id });
submitRsp.IsSuccessStatusCode.ShouldBeTrue();

// 4. Verify Admin sees it in Pending list
// Verify Admin sees it in Pending list
var (adminListRsp, pendingGuides) = await App.ModeratorClient.GETAsync<AdminPendingGuidesEndpoint, List<GuideDto>>();
adminListRsp.IsSuccessStatusCode.ShouldBeTrue();
pendingGuides.ShouldContain(g => g.Id == created.Id);

// 5. Verify Public still sees the OLD version (Title should NOT be "Updated Title Pending")
// Verify Public still sees the OLD version (Title should NOT be "Updated Title Pending")
var (publicListRsp, publicGuides) = await App.AnonymousClient.GETAsync<ListGuidesEndpoint, ListGuidesRequest, List<GuideDto>>(
new ListGuidesRequest());
publicListRsp.IsSuccessStatusCode.ShouldBeTrue();
var publicGuide = publicGuides!.FirstOrDefault(g => g.Id == created.Id);
publicGuide.ShouldNotBeNull();
publicGuide.Title.ShouldNotBe("Updated Title Pending"); // Helper likely doesn't verify exact content, but existence is key

// 6. Admin Approves
// Admin Approves
await App.ModeratorClient.POSTAsync<ApproveGuideEndpoint, ApproveGuideRequest>(
new ApproveGuideRequest { GuideId = created.Id });

// 7. Verify Public sees NEW version
// Verify Public sees NEW version
var (finalListRsp, finalGuides) = await App.AnonymousClient.GETAsync<ListGuidesEndpoint, ListGuidesRequest, List<GuideDto>>(
new ListGuidesRequest());
finalGuides!.First(g => g.Id == created.Id).Title.ShouldBe("Updated Title Pending");
}

[Fact, Priority(23)]
public async Task UpdateGuide_Published_AsAuthor_Succeeds()
{
// Create and publish a guide
var (createRsp, created) = await App.RegularUserClient.POSTAsync<CreateGuideEndpoint, CreateGuideRequest, GuideDto>(
new CreateGuideRequest { Type = GuideType.General });
createRsp.IsSuccessStatusCode.ShouldBeTrue();

await App.RegularUserClient.POSTAsync<SubmitGuideForApprovalEndpoint, SubmitGuideRequest>(
new SubmitGuideRequest { GuideId = created!.Id });
await App.ModeratorClient.POSTAsync<ApproveGuideEndpoint, ApproveGuideRequest>(
new ApproveGuideRequest { GuideId = created.Id });

// Author tries to update - should succeed
var updateRsp = await App.RegularUserClient.PUTAsync<UpdateGuideEndpoint, UpdateGuideRequest>(
new UpdateGuideRequest
{
Id = created.Id,
Title = "New Draft Title",
Description = "New Draft Desc",
MarkdownContent = "# New Draft Content"
});

updateRsp.IsSuccessStatusCode.ShouldBeTrue();
}

protected override async ValueTask TearDownAsync()
{
await App.CleanUpGuidesAsync();
Expand Down
13 changes: 12 additions & 1 deletion EliteAPI/Features/Guides/Endpoints/CreateCommentEndpoint.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using EliteAPI.Features.Guides.Services;
using EliteAPI.Features.AuditLogs.Services;
using EliteAPI.Features.Comments.Mappers;
using EliteAPI.Features.Comments.Models.Dtos;
using EliteAPI.Features.Common.Services;
using EliteAPI.Utilities;
using FastEndpoints;
using FluentValidation;
Expand All @@ -9,7 +11,8 @@ namespace EliteAPI.Features.Guides.Endpoints;

public class CreateCommentEndpoint(
CommentService commentService,
CommentMapper mapper) : Endpoint<CreateCommentRequest, CommentDto>
CommentMapper mapper,
AuditLogService auditLogService) : Endpoint<CreateCommentRequest, CommentDto>
{
public override void Configure()
{
Expand All @@ -34,6 +37,14 @@ public override async Task HandleAsync(CreateCommentRequest req, CancellationTok
{
var comment = await commentService.AddCommentAsync(req.GuideId, EliteAPI.Features.Comments.Models.CommentTargetType.Guide, userId.Value, req.Content, req.ParentId, req.LiftedElementId);

var guideSlug = SqidService.Encode(req.GuideId);
await auditLogService.LogAsync(
userId.Value,
"comment_submitted",
"Guide",
guideSlug,
$"Commented on guide {guideSlug}");

await Send.OkAsync(mapper.ToDto(comment, userId.Value, User.IsSupportOrHigher(), null), ct);
}
catch (UnauthorizedAccessException ex)
Expand Down
23 changes: 22 additions & 1 deletion EliteAPI/Features/Guides/Endpoints/GuideApprovalEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ namespace EliteAPI.Features.Guides.Endpoints;
/// <summary>
/// Submit a guide for approval (author only)
/// </summary>
public class SubmitGuideForApprovalEndpoint(GuideService guideService, UserManager userManager) : Endpoint<SubmitGuideRequest>
public class SubmitGuideForApprovalEndpoint(
GuideService guideService,
UserManager userManager,
AuditLogService auditLogService,
NotificationService notificationService) : Endpoint<SubmitGuideRequest>
{
public override void Configure()
{
Expand Down Expand Up @@ -59,6 +63,23 @@ public override async Task HandleAsync(SubmitGuideRequest req, CancellationToken
}

await guideService.SubmitForApprovalAsync(req.GuideId);

var guideSlug = guideService.GetSlug(guide.Id);

await auditLogService.LogAsync(
user.AccountId!.Value,
"guide_submitted",
"Guide",
guideSlug,
"Submitted guide for approval");

await notificationService.CreateAsync(
user.AccountId!.Value,
NotificationType.GuideSubmitted,
"Guide Submitted",
$"**{guide.DraftVersion?.Title}** has been submitted for approval!",
$"/guides/{guideSlug}?draft=true");

await Send.NoContentAsync(ct);
}
}
Expand Down
4 changes: 2 additions & 2 deletions EliteAPI/Features/Guides/Services/GuideService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,12 @@ public async Task<Guide> CreateDraftAsync(ulong authorId, GuideType type)

public string GetSlug(int id)
{
return EliteAPI.Features.Common.Services.SqidService.Encode(id);
return Common.Services.SqidService.Encode(id);
}

public int? GetIdFromSlug(string slug)
{
return EliteAPI.Features.Common.Services.SqidService.Decode(slug);
return Common.Services.SqidService.Decode(slug);
}

public async Task UpdateDraftAsync(int guideId, string title, string description, string markdown, string? iconSkyblockId, List<string>? tags,
Expand Down
35 changes: 35 additions & 0 deletions EliteAPI/Features/Monetization/Services/MonetizationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
using FastEndpoints;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using EliteAPI.Features.AuditLogs.Services;
using EliteAPI.Features.Notifications.Models;
using EliteAPI.Features.Notifications.Services;
using Microsoft.Extensions.Options;
using StackExchange.Redis;

Expand All @@ -27,6 +30,8 @@ public class MonetizationService(
ILogger<MonetizationService> logger,
IBadgeService badgeService,
IMessageService messageService,
NotificationService notificationService,
AuditLogService auditLogService,
IOptions<ConfigCooldownSettings> coolDowns)
: IMonetizationService
{
Expand Down Expand Up @@ -140,6 +145,20 @@ public async Task GrantProductAccessAsync(ulong userId, ulong productId) {
);

await context.SaveChangesAsync();

await auditLogService.LogAsync(
userId,
"shop_claim",
"Product",
productId.ToString(),
$"Claimed product {productId} {product?.Name ?? "Item"}");

await notificationService.CreateAsync(
userId,
NotificationType.ShopPurchase,
"Claim Successful",
$"You have claimed **{product?.Name ?? "Item"}**! Use your perk(s) in your account settings!",
$"/profile/settings");
}

public async Task<ActionResult> GrantTestEntitlementAsync(ulong targetId, ulong productId,
Expand Down Expand Up @@ -435,6 +454,22 @@ public async Task SyncDiscordEntitlementsAsync(ulong entityId, bool isGuild) {
discordEntitlement.UserId.ToString() ?? string.Empty,
discordEntitlement.ProductId.ToString()
);

var product = await context.Products.FindAsync(discordEntitlement.ProductId);

await auditLogService.LogAsync(
discordEntitlement.UserId!.Value,
"shop_purchase",
"Product",
discordEntitlement.ProductId.ToString(),
$"Purchased product {discordEntitlement.ProductId} {product?.Name ?? "Item"}");

await notificationService.CreateAsync(
discordEntitlement.UserId.Value,
NotificationType.ShopPurchase,
"Purchase Successful",
$"Thank you for purchasing **{product?.Name ?? "Item"}**! Use your perk(s) in your account settings!",
$"/profile/settings");
}

context.ProductAccesses.Add(access);
Expand Down
3 changes: 2 additions & 1 deletion EliteAPI/Features/Notifications/Models/NotificationType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ public enum NotificationType
CommentRejected = 7,
NewComment = 8,
NewReply = 9,
ShopPurchase = 10
ShopPurchase = 10,
GuideSubmitted = 11
}
Loading