Skip to content

Commit

Permalink
Merge pull request #31804 from bdach/bss/api-setup
Browse files Browse the repository at this point in the history
Add API request & response structures for beatmap submission
  • Loading branch information
peppy authored Feb 7, 2025
2 parents 9af5ebb + 41c8f64 commit cf4b501
Show file tree
Hide file tree
Showing 37 changed files with 310 additions and 73 deletions.
2 changes: 1 addition & 1 deletion osu.Desktop/DiscordRichPresence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ private void updatePresence(bool hideIdentifiableInformation)
new Button
{
Label = "View beatmap",
Url = $@"{api.WebsiteRootUrl}/beatmaps/{beatmapId}?mode={ruleset.Value.ShortName}"
Url = $@"{api.Endpoints.WebsiteUrl}/beatmaps/{beatmapId}?mode={ruleset.Value.ShortName}"
}
};
}
Expand Down
2 changes: 1 addition & 1 deletion osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public void TestOnlineMenuBannerTrusted()
new APIMenuImage
{
Image = @"https://assets.ppy.sh/main-menu/[email protected]",
Url = $@"{API.WebsiteRootUrl}/home/news/2023-12-21-project-loved-december-2023",
Url = $@"{API.Endpoints.WebsiteUrl}/home/news/2023-12-21-project-loved-december-2023",
}
}
});
Expand Down
10 changes: 5 additions & 5 deletions osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,19 +67,19 @@ public void Setup() => Schedule(() =>
[Test]
public void TestLink()
{
AddStep("set current path", () => markdownContainer.CurrentPath = $"{API.WebsiteRootUrl}/wiki/Article_styling_criteria/");
AddStep("set current path", () => markdownContainer.CurrentPath = $"{API.Endpoints.WebsiteUrl}/wiki/Article_styling_criteria/");

AddStep("set '/wiki/Main_page''", () => markdownContainer.Text = "[wiki main page](/wiki/Main_page)");
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Main_page");
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.Endpoints.WebsiteUrl}/wiki/Main_page");

AddStep("set '../FAQ''", () => markdownContainer.Text = "[FAQ](../FAQ)");
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/FAQ");
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.Endpoints.WebsiteUrl}/wiki/FAQ");

AddStep("set './Writing''", () => markdownContainer.Text = "[wiki writing guidline](./Writing)");
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Article_styling_criteria/Writing");
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.Endpoints.WebsiteUrl}/wiki/Article_styling_criteria/Writing");

AddStep("set 'Formatting''", () => markdownContainer.Text = "[wiki formatting guidline](Formatting)");
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Article_styling_criteria/Formatting");
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.Endpoints.WebsiteUrl}/wiki/Article_styling_criteria/Formatting");
}

[Test]
Expand Down
2 changes: 1 addition & 1 deletion osu.Game/Beatmaps/BeatmapInfoExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public static bool Match(this IBeatmapInfo beatmapInfo, params FilterCriteria.Op
if (beatmapInfo.OnlineID <= 0 || beatmapInfo.BeatmapSet == null)
return null;

return $@"{api.WebsiteRootUrl}/beatmapsets/{beatmapInfo.BeatmapSet.OnlineID}#{ruleset?.ShortName ?? beatmapInfo.Ruleset.ShortName}/{beatmapInfo.OnlineID}";
return $@"{api.Endpoints.WebsiteUrl}/beatmapsets/{beatmapInfo.BeatmapSet.OnlineID}#{ruleset?.ShortName ?? beatmapInfo.Ruleset.ShortName}/{beatmapInfo.OnlineID}";
}
}
}
4 changes: 2 additions & 2 deletions osu.Game/Beatmaps/BeatmapSetInfoExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ public static class BeatmapSetInfoExtensions
return null;

if (ruleset != null)
return $@"{api.WebsiteRootUrl}/beatmapsets/{beatmapSetInfo.OnlineID}#{ruleset.ShortName}";
return $@"{api.Endpoints.WebsiteUrl}/beatmapsets/{beatmapSetInfo.OnlineID}#{ruleset.ShortName}";

return $@"{api.WebsiteRootUrl}/beatmapsets/{beatmapSetInfo.OnlineID}";
return $@"{api.Endpoints.WebsiteUrl}/beatmapsets/{beatmapSetInfo.OnlineID}";
}
}
}
15 changes: 6 additions & 9 deletions osu.Game/Online/API/APIAccess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ public partial class APIAccess : Component, IAPIProvider

private readonly Queue<APIRequest> queue = new Queue<APIRequest>();

public string APIEndpointUrl { get; }

public string WebsiteRootUrl { get; }
public EndpointConfiguration Endpoints { get; }

/// <summary>
/// The API response version.
Expand Down Expand Up @@ -75,7 +73,7 @@ public partial class APIAccess : Component, IAPIProvider
private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource();
private readonly Logger log;

public APIAccess(OsuGameBase game, OsuConfigManager config, EndpointConfiguration endpointConfiguration, string versionHash)
public APIAccess(OsuGameBase game, OsuConfigManager config, EndpointConfiguration endpoints, string versionHash)
{
this.game = game;
this.config = config;
Expand All @@ -89,14 +87,13 @@ public APIAccess(OsuGameBase game, OsuConfigManager config, EndpointConfiguratio
APIVersion = now.Year * 10000 + now.Month * 100 + now.Day;
}

APIEndpointUrl = endpointConfiguration.APIEndpointUrl;
WebsiteRootUrl = endpointConfiguration.WebsiteRootUrl;
Endpoints = endpoints;
NotificationsClient = setUpNotificationsClient();

authentication = new OAuth(endpointConfiguration.APIClientID, endpointConfiguration.APIClientSecret, APIEndpointUrl);
authentication = new OAuth(endpoints.APIClientID, endpoints.APIClientSecret, Endpoints.APIUrl);

log = Logger.GetLogger(LoggingTarget.Network);
log.Add($@"API endpoint root: {APIEndpointUrl}");
log.Add($@"API endpoint root: {Endpoints.APIUrl}");
log.Add($@"API request version: {APIVersion}");

ProvidedUsername = config.Get<string>(OsuSetting.Username);
Expand Down Expand Up @@ -408,7 +405,7 @@ public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email,

var req = new RegistrationRequest
{
Url = $@"{APIEndpointUrl}/users",
Url = $@"{Endpoints.APIUrl}/users",
Method = HttpMethod.Post,
Username = username,
Email = email,
Expand Down
2 changes: 1 addition & 1 deletion osu.Game/Online/API/APIRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public abstract class APIRequest

protected virtual WebRequest CreateWebRequest() => new OsuWebRequest(Uri);

protected virtual string Uri => $@"{API!.APIEndpointUrl}/api/v2/{Target}";
protected virtual string Uri => $@"{API!.Endpoints.APIUrl}/api/v2/{Target}";

protected IAPIProvider? API;

Expand Down
8 changes: 5 additions & 3 deletions osu.Game/Online/API/DummyAPIAccess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@ public partial class DummyAPIAccess : Component, IAPIProvider

public string ProvidedUsername => LocalUser.Value.Username;

public string APIEndpointUrl => "http://localhost";

public string WebsiteRootUrl => "http://localhost";
public EndpointConfiguration Endpoints { get; } = new EndpointConfiguration
{
APIUrl = "http://localhost",
WebsiteUrl = "http://localhost",
};

public int APIVersion => int.Parse(DateTime.Now.ToString("yyyyMMdd"));

Expand Down
9 changes: 2 additions & 7 deletions osu.Game/Online/API/IAPIProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,9 @@ public interface IAPIProvider
string ProvidedUsername { get; }

/// <summary>
/// The URL endpoint for this API. Does not include a trailing slash.
/// Holds configuration for online endpoints.
/// </summary>
string APIEndpointUrl { get; }

/// <summary>
/// The root URL of the website, excluding the trailing slash.
/// </summary>
string WebsiteRootUrl { get; }
EndpointConfiguration Endpoints { get; }

/// <summary>
/// The version of the API.
Expand Down
26 changes: 26 additions & 0 deletions osu.Game/Online/API/Requests/APIUploadRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System.Diagnostics;
using osu.Framework.IO.Network;

namespace osu.Game.Online.API.Requests
{
public abstract class APIUploadRequest : APIRequest
{
protected override WebRequest CreateWebRequest()
{
var request = base.CreateWebRequest();
request.UploadProgress += onUploadProgress;
return request;
}

private void onUploadProgress(long current, long total)
{
Debug.Assert(API != null);
API.Schedule(() => Progressed?.Invoke(current, total));
}

public event APIProgressHandler? Progressed;
}
}
55 changes: 55 additions & 0 deletions osu.Game/Online/API/Requests/PatchBeatmapPackageRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using System.Net.Http;
using osu.Framework.IO.Network;

namespace osu.Game.Online.API.Requests
{
public class PatchBeatmapPackageRequest : APIUploadRequest
{
protected override string Uri
{
get
{
// can be removed once the service has been successfully deployed to production
if (API!.Endpoints.BeatmapSubmissionServiceUrl == null)
throw new NotSupportedException("Beatmap submission not supported in this configuration!");

return $@"{API!.Endpoints.BeatmapSubmissionServiceUrl!}/beatmapsets/{BeatmapSetID}";
}
}

protected override string Target => throw new NotSupportedException();

public uint BeatmapSetID { get; }

// ReSharper disable once CollectionNeverUpdated.Global
public Dictionary<string, byte[]> FilesChanged { get; } = new Dictionary<string, byte[]>();

// ReSharper disable once CollectionNeverUpdated.Global
public HashSet<string> FilesDeleted { get; } = new HashSet<string>();

public PatchBeatmapPackageRequest(uint beatmapSetId)
{
BeatmapSetID = beatmapSetId;
}

protected override WebRequest CreateWebRequest()
{
var request = base.CreateWebRequest();
request.Method = HttpMethod.Patch;

foreach ((string filename, byte[] content) in FilesChanged)
request.AddFile(@"filesChanged", content, filename);

foreach (string filename in FilesDeleted)
request.AddParameter(@"filesDeleted", filename, RequestParameterType.Form);

request.Timeout = 60_000;
return request;
}
}
}
82 changes: 82 additions & 0 deletions osu.Game/Online/API/Requests/PutBeatmapSetRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using osu.Framework.IO.Network;
using osu.Framework.Localisation;
using osu.Game.Localisation;
using osu.Game.Online.API.Requests.Responses;

namespace osu.Game.Online.API.Requests
{
public class PutBeatmapSetRequest : APIRequest<PutBeatmapSetResponse>
{
protected override string Uri
{
get
{
// can be removed once the service has been successfully deployed to production
if (API!.Endpoints.BeatmapSubmissionServiceUrl == null)
throw new NotSupportedException("Beatmap submission not supported in this configuration!");

return $@"{API!.Endpoints.BeatmapSubmissionServiceUrl}/beatmapsets";
}
}

protected override string Target => throw new NotSupportedException();

[JsonProperty("beatmapset_id")]
public uint? BeatmapSetID { get; init; }

[JsonProperty("beatmaps_to_create")]
public uint BeatmapsToCreate { get; init; }

[JsonProperty("beatmaps_to_keep")]
public uint[] BeatmapsToKeep { get; init; } = [];

[JsonProperty("target")]
public BeatmapSubmissionTarget SubmissionTarget { get; init; }

private PutBeatmapSetRequest()
{
}

public static PutBeatmapSetRequest CreateNew(uint beatmapCount, BeatmapSubmissionTarget target) => new PutBeatmapSetRequest
{
BeatmapsToCreate = beatmapCount,
SubmissionTarget = target,
};

public static PutBeatmapSetRequest UpdateExisting(uint beatmapSetId, IEnumerable<uint> beatmapsToKeep, uint beatmapsToCreate, BeatmapSubmissionTarget target) => new PutBeatmapSetRequest
{
BeatmapSetID = beatmapSetId,
BeatmapsToKeep = beatmapsToKeep.ToArray(),
BeatmapsToCreate = beatmapsToCreate,
SubmissionTarget = target,
};

protected override WebRequest CreateWebRequest()
{
var req = base.CreateWebRequest();
req.Method = HttpMethod.Put;
req.ContentType = @"application/json";
req.AddRaw(JsonConvert.SerializeObject(this));
return req;
}
}

[JsonConverter(typeof(StringEnumConverter))]
public enum BeatmapSubmissionTarget
{
[LocalisableDescription(typeof(BeatmapSubmissionStrings), nameof(BeatmapSubmissionStrings.BeatmapSubmissionTargetWIP))]
WIP,

[LocalisableDescription(typeof(BeatmapSubmissionStrings), nameof(BeatmapSubmissionStrings.BeatmapSubmissionTargetPending))]
Pending,
}
}
45 changes: 45 additions & 0 deletions osu.Game/Online/API/Requests/ReplaceBeatmapPackageRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Net.Http;
using osu.Framework.IO.Network;

namespace osu.Game.Online.API.Requests
{
public class ReplaceBeatmapPackageRequest : APIUploadRequest
{
protected override string Uri
{
get
{
// can be removed once the service has been successfully deployed to production
if (API!.Endpoints.BeatmapSubmissionServiceUrl == null)
throw new NotSupportedException("Beatmap submission not supported in this configuration!");

return $@"{API!.Endpoints.BeatmapSubmissionServiceUrl}/beatmapsets/{BeatmapSetID}";
}
}

protected override string Target => throw new NotSupportedException();

public uint BeatmapSetID { get; }

private readonly byte[] oszPackage;

public ReplaceBeatmapPackageRequest(uint beatmapSetID, byte[] oszPackage)
{
this.oszPackage = oszPackage;
BeatmapSetID = beatmapSetID;
}

protected override WebRequest CreateWebRequest()
{
var request = base.CreateWebRequest();
request.AddFile(@"beatmapArchive", oszPackage);
request.Method = HttpMethod.Put;
request.Timeout = 60_000;
return request;
}
}
}
30 changes: 30 additions & 0 deletions osu.Game/Online/API/Requests/Responses/PutBeatmapSetResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace osu.Game.Online.API.Requests.Responses
{
public class PutBeatmapSetResponse
{
[JsonProperty("beatmapset_id")]
public uint BeatmapSetId { get; set; }

[JsonProperty("beatmap_ids")]
public ICollection<uint> BeatmapIds { get; set; } = Array.Empty<uint>();

[JsonProperty("files")]
public ICollection<BeatmapSetFile> Files { get; set; } = Array.Empty<BeatmapSetFile>();
}

public struct BeatmapSetFile
{
[JsonProperty("filename")]
public string Filename { get; set; }

[JsonProperty("sha2_hash")]
public string SHA2Hash { get; set; }
}
}
Loading

0 comments on commit cf4b501

Please sign in to comment.