Skip to content

Commit

Permalink
Finish save editor backend (#1051)
Browse files Browse the repository at this point in the history
- Implement endpoint to add presents
- Sort dropdowns
  • Loading branch information
SapiensAnatis authored Aug 27, 2024
1 parent 82d97eb commit 8f25035
Show file tree
Hide file tree
Showing 14 changed files with 269 additions and 39 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Net;
using System.Net.Http.Json;
using DragaliaAPI.Features.Web.Savefile;
using DragaliaAPI.Shared.Features.Presents;

namespace DragaliaAPI.Integration.Test.Features.Web.Savefile;

Expand Down Expand Up @@ -47,4 +48,81 @@ public async Task PresentWidgetData_ReturnsData()
]
);
}

[Fact]
public async Task Edit_Unauthenticated_Returns401() =>
(await this.Client.PostAsync("/api/savefile/edit", null))
.Should()
.HaveStatusCode(HttpStatusCode.Unauthorized);

[Fact]
public async Task Edit_Invalid_ReturnsBadRequest()
{
this.AddTokenCookie();

SavefileEditRequest invalidRequest =
new()
{
Presents =
[
new()
{
Type = EntityTypes.Chara,
Item = -4,
Quantity = -2,
},
],
};

(await this.Client.PostAsJsonAsync("/api/savefile/edit", invalidRequest))
.Should()
.HaveStatusCode(HttpStatusCode.BadRequest);
}

[Fact]
public async Task Edit_Valid_AddsPresents()
{
this.AddTokenCookie();

SavefileEditRequest request =
new()
{
Presents =
[
new()
{
Type = EntityTypes.Chara,
Item = (int)Charas.SummerCelliera,
Quantity = 1,
},
],
};

(await this.Client.PostAsJsonAsync("/api/savefile/edit", request))
.Should()
.HaveStatusCode(HttpStatusCode.OK);

DragaliaResponse<PresentGetPresentListResponse> presentList =
await this.Client.PostMsgpack<PresentGetPresentListResponse>(
"/present/get_present_list",
new PresentGetPresentListRequest { PresentId = 0 }
);

presentList
.Data.PresentList.Should()
.BeEquivalentTo(
[
new PresentDetailList()
{
EntityType = EntityTypes.Chara,
EntityId = (int)Charas.SummerCelliera,
EntityQuantity = 1,
EntityLevel = 1,
ReceiveLimitTime = DateTimeOffset.UnixEpoch,
MessageId = PresentMessage.DragaliaLostTeamGift,
},
],
opts => opts.Excluding(x => x.PresentId).Excluding(x => x.CreateTime)
);
}
}
3 changes: 0 additions & 3 deletions DragaliaAPI/DragaliaAPI/Features/Present/PresentController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@
namespace DragaliaAPI.Features.Present;

[Route("present")]
[Consumes("application/octet-stream")]
[Produces("application/octet-stream")]
[ApiController]
public class PresentController : DragaliaControllerBase
{
private readonly IPresentControllerService presentControllerService;
Expand Down
4 changes: 3 additions & 1 deletion DragaliaAPI/DragaliaAPI/Features/Web/FeatureExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Diagnostics;
using DragaliaAPI.Features.Web;
using DragaliaAPI.Features.Web.News;
using DragaliaAPI.Features.Web.Savefile;
using DragaliaAPI.Features.Web.Users;
using DragaliaAPI.Shared.PlayerDetails;
using Microsoft.AspNetCore.Authentication.JwtBearer;
Expand All @@ -17,7 +18,8 @@ public static IServiceCollection AddWebFeature(this IServiceCollection serviceCo
serviceCollection
.AddTransient<IConfigureOptions<JwtBearerOptions>, ConfigureJwtBearerOptions>()
.AddScoped<UserService>()
.AddScoped<NewsService>();
.AddScoped<NewsService>()
.AddScoped<SavefileEditService>();

serviceCollection
.AddAuthentication()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

namespace DragaliaAPI.Features.Web.News;

[Route("/api/news")]
[ApiController]
[Route("/api/news")]
[AllowAnonymous]
public sealed class NewsController(NewsService newsService) : ControllerBase
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

namespace DragaliaAPI.Features.Web.Savefile;

[ApiController]
[Route("/api/savefile")]
[Authorize(Policy = AuthConstants.PolicyNames.RequireDawnshardIdentity)]
internal sealed class SavefileController(ILoadService loadService) : ControllerBase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

namespace DragaliaAPI.Features.Web.Savefile;

[ApiController]
[Route("/api/savefile/edit")]
[Authorize(Policy = AuthConstants.PolicyNames.RequireDawnshardIdentity)]
internal sealed class SavefileEditController : ControllerBase
internal sealed class SavefileEditController(SavefileEditService savefileEditService)
: ControllerBase
{
[SuppressMessage(
"Performance",
Expand All @@ -16,4 +18,17 @@ internal sealed class SavefileEditController : ControllerBase
[HttpGet("widgets/present")]
public ActionResult<PresentWidgetData> GetPresentWidgetData() =>
EditorWidgetsService.GetPresentWidgetData();

[HttpPost]
public async Task<ActionResult> DoEdit(SavefileEditRequest request)
{
if (!savefileEditService.ValidateEdits(request))
{
return this.BadRequest();
}

await savefileEditService.PerformEdits(request);

return this.Ok();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Text.Json.Serialization;
using DragaliaAPI.Shared.Definitions.Enums;

namespace DragaliaAPI.Features.Web.Savefile;

public class SavefileEditRequest
{
public required List<PresentFormSubmission> Presents { get; init; }
}

public class PresentFormSubmission
{
[JsonConverter(typeof(JsonStringEnumConverter<EntityTypes>))]
public EntityTypes Type { get; init; }

public int Item { get; init; }

public int Quantity { get; init; }
}
117 changes: 117 additions & 0 deletions DragaliaAPI/DragaliaAPI/Features/Web/Savefile/SavefileEditService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using DragaliaAPI.Database;
using DragaliaAPI.Features.Present;
using DragaliaAPI.Shared.Definitions.Enums;
using DragaliaAPI.Shared.Features.Presents;
using DragaliaAPI.Shared.MasterAsset;

namespace DragaliaAPI.Features.Web.Savefile;

internal sealed partial class SavefileEditService(
IPresentService presentService,
ApiContext apiContext,
ILogger<SavefileEditService> logger
)
{
public bool ValidateEdits(SavefileEditRequest request) => ValidatePresents(request.Presents);

public async Task PerformEdits(SavefileEditRequest request)
{
foreach (PresentFormSubmission presentFormSubmission in request.Presents)
{
presentService.AddPresent(
new Present.Present(
PresentMessage.DragaliaLostTeamGift, // Could consider adding a new message to the master asset
presentFormSubmission.Type,
presentFormSubmission.Item,
presentFormSubmission.Quantity
)
);
}

Log.AddedPresents(logger, request.Presents.Count);

await apiContext.SaveChangesAsync();

Log.EditSuccessful(logger);
}

private bool ValidatePresents(List<PresentFormSubmission> presents)
{
if (presents.Count > 100)
{
Log.PresentLimitExceeded(logger, presents.Count);
return false;
}

foreach (PresentFormSubmission present in presents)
{
if (present.Quantity < 1)
{
Log.InvalidSinglePresent(logger, present);
return false;
}

bool idValid = present.Type switch
{
EntityTypes.Chara => ValidateCharaPresent(present),
EntityTypes.Dragon => ValidateDragonPresent(present),
EntityTypes.Item => ValidateItemPresent(present),
EntityTypes.Material => ValidateMaterialPresent(present),
EntityTypes.DmodePoint => ValidateDmodePointPresent(present),
EntityTypes.SkipTicket => ValidateSkipTicketPresent(present),
EntityTypes.DragonGift => ValidateDragonGiftPresent(present),
_ => false,
};

if (!idValid)
{
Log.InvalidSinglePresent(logger, present);
return false;
}
}

return true;
}

private static bool ValidateCharaPresent(PresentFormSubmission present) =>
MasterAsset.CharaData.ContainsKey((Charas)present.Item) && present.Quantity == 1;

private static bool ValidateDragonPresent(PresentFormSubmission present) =>
MasterAsset.DragonData.ContainsKey((Dragons)present.Item);

private static bool ValidateItemPresent(PresentFormSubmission present) =>
MasterAsset.UseItem.ContainsKey((UseItem)present.Item);

private static bool ValidateMaterialPresent(PresentFormSubmission present) =>
MasterAsset.MaterialData.ContainsKey((Materials)present.Item);

private static bool ValidateDmodePointPresent(PresentFormSubmission present) =>
(DmodePoint)present.Item is DmodePoint.Point1 or DmodePoint.Point2;

private static bool ValidateSkipTicketPresent(PresentFormSubmission present) =>
present.Item == 0;

private static bool ValidateDragonGiftPresent(PresentFormSubmission present) =>
Enum.IsDefined((DragonGifts)present.Item);

private static partial class Log
{
[LoggerMessage(
LogLevel.Information,
"Request was invalid: {Count} presents exceeds limit of 100"
)]
public static partial void PresentLimitExceeded(ILogger logger, int count);

[LoggerMessage(LogLevel.Information, "Request was invalid: present {@Present} was invalid")]
public static partial void InvalidSinglePresent(
ILogger logger,
PresentFormSubmission present
);

[LoggerMessage(LogLevel.Information, "Added {Count} presents to the gift box")]
public static partial void AddedPresents(ILogger logger, int count);

[LoggerMessage(LogLevel.Information, "Savefile edited successfully")]
public static partial void EditSuccessful(ILogger logger);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

namespace DragaliaAPI.Features.Web.Users;

[Route("/api/user")]
[ApiController]
[Route("/api/user")]
public class UserController(UserService userService, ILogger<UserController> logger)
: ControllerBase
{
Expand Down
1 change: 0 additions & 1 deletion Website/.env
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,5 @@ PUBLIC_ENABLE_MSW=false # Disable Mock Service worker for mock API calls. Never
PUBLIC_BAAS_URL=https://baas.lukefz.xyz/ # The URL of the BaaS server to use for logins.
PUBLIC_BAAS_CLIENT_ID=dawnshard # The client ID to present to the BaaS during OAuth.
PUBLIC_VERSION=0.0.0 # A version number to add in the HEAD. Set by NerdBank.GitVersioning during a CI build.
PUBLIC_ENABLE_SAVE_EDITOR=false

DAWNSHARD_API_URL_SSR=http://localhost:5000/ # The internal URL of the main DragaliaAPI C# server.
1 change: 0 additions & 1 deletion Website/.env.development
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
PUBLIC_ENABLE_MSW=true # Enables Mock Service Worker for API calls and decreases cookie security.
PUBLIC_VERSION=development
PUBLIC_ENABLE_SAVE_EDITOR=true

DAWNSHARD_API_URL_SSR=http://localhost:5000/
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,26 @@
const keyPrefix = 'saveEditor.present';
$: types = widgetData.types.map(({ type }) => ({
value: type,
label: $t(`${keyPrefix}.type.${type}.label`)
}));
$: availableItems = getAvailableItems(typeValue);
let disableQuantity = false;
let typeValue: EntityType | '';
let itemValue: number | '';
let quantityValue: number = 1;
const form = createForm();
const type = form.field();
const item = form.field();
const quantity = form.field();
$: types = widgetData.types
.map(({ type }) => ({
value: type,
label: $t(`${keyPrefix}.type.${type}.label`)
}))
.sort((a, b) => a.label.localeCompare(b.label));
$: availableItems = getAvailableItems(typeValue);
const getAvailableItems = (type: EntityType | '') => {
if (!type) {
return [];
Expand All @@ -39,10 +52,12 @@
return [];
}
return itemList.map(({ id }) => ({
value: id,
label: $t(`${keyPrefix}.type.${typeValue}.item.${id}`)
}));
return itemList
.map(({ id }) => ({
value: id,
label: $t(`${keyPrefix}.type.${typeValue}.item.${id}`)
}))
.sort((a, b) => a.label.localeCompare(b.label));
};
const onSubmit = () => {
Expand All @@ -68,15 +83,6 @@
itemValue = '';
};
let typeValue: EntityType | '';
let itemValue: number | '';
let quantityValue: number = 1;
const form = createForm();
const type = form.field();
const item = form.field();
const quantity = form.field();
</script>

<Card.Root>
Expand Down
Loading

0 comments on commit 8f25035

Please sign in to comment.