From 6bc2b8d57c1257a8ee13e6898bd08d29f2020b5b Mon Sep 17 00:00:00 2001 From: Laura Hausmann Date: Mon, 1 Apr 2024 20:22:45 +0200 Subject: [PATCH] [frontend] Bootstrap shared DTOs, API abstractions, SignalR & more --- .../Controllers/AdminController.cs | 2 +- .../Controllers/AuthController.cs | 2 +- .../Controllers/FallbackController.cs | 2 +- .../Federation/ActivityPubController.cs | 2 +- .../Federation/WellKnownController.cs | 2 +- .../Controllers/NoteController.cs | 4 +- .../Controllers/NotificationController.cs | 1 + .../Controllers/Renderers/NoteRenderer.cs | 2 +- .../Renderers/NotificationRenderer.cs | 2 +- .../Renderers/UserProfileRenderer.cs | 11 ++- .../Controllers/Renderers/UserRenderer.cs | 2 +- .../Schemas/UserProfileResponse.cs | 15 ---- .../Controllers/TimelineController.cs | 1 + .../Controllers/UserController.cs | 1 + .../Core/Database/Tables/UserProfile.cs | 6 +- .../Core/Extensions/QueryableExtensions.cs | 1 + .../Core/Extensions/ServiceExtensions.cs | 2 +- .../Core/Middleware/ErrorHandlerMiddleware.cs | 2 +- Iceshrimp.Backend/Hubs/ExampleHub.cs | 10 +++ Iceshrimp.Backend/Iceshrimp.Backend.csproj | 2 + Iceshrimp.Backend/Startup.cs | 7 ++ .../ControllerModels/NoteControllerModel.cs | 26 +++++++ .../Core/Extensions/HttpClientExtensions.cs | 59 +++++++++++++++ .../HttpResponseMessageExtensions.cs | 6 ++ .../Core/Miscellaneous/ApiException.cs | 8 +++ .../Core/Services/ApiService.cs | 8 +++ Iceshrimp.Frontend/Iceshrimp.Frontend.csproj | 9 +++ Iceshrimp.Frontend/Pages/Counter.razor | 2 +- Iceshrimp.Frontend/Pages/Hub.razor | 72 +++++++++++++++++++ Iceshrimp.Frontend/Pages/TestNote.razor | 44 ++++++++++++ Iceshrimp.Frontend/Program.cs | 2 + Iceshrimp.NET.sln | 6 ++ Iceshrimp.Shared/Iceshrimp.Shared.csproj | 9 +++ .../Schemas/AuthRequest.cs | 2 +- .../Schemas/AuthResponse.cs | 2 +- .../Schemas/ErrorResponse.cs | 2 +- .../Schemas/InviteResponse.cs | 2 +- .../Schemas/NoteCreateRequest.cs | 2 +- .../Schemas/NoteResponse.cs | 2 +- .../Schemas/NotificationResponse.cs | 2 +- .../Schemas/UserProfileResponse.cs | 21 ++++++ .../Schemas/UserResponse.cs | 2 +- .../Schemas/ValueResponse.cs | 2 +- 43 files changed, 327 insertions(+), 42 deletions(-) delete mode 100644 Iceshrimp.Backend/Controllers/Schemas/UserProfileResponse.cs create mode 100644 Iceshrimp.Backend/Hubs/ExampleHub.cs create mode 100644 Iceshrimp.Frontend/Core/ControllerModels/NoteControllerModel.cs create mode 100644 Iceshrimp.Frontend/Core/Extensions/HttpClientExtensions.cs create mode 100644 Iceshrimp.Frontend/Core/Extensions/HttpResponseMessageExtensions.cs create mode 100644 Iceshrimp.Frontend/Core/Miscellaneous/ApiException.cs create mode 100644 Iceshrimp.Frontend/Core/Services/ApiService.cs create mode 100644 Iceshrimp.Frontend/Pages/Hub.razor create mode 100644 Iceshrimp.Frontend/Pages/TestNote.razor create mode 100644 Iceshrimp.Shared/Iceshrimp.Shared.csproj rename {Iceshrimp.Backend/Controllers => Iceshrimp.Shared}/Schemas/AuthRequest.cs (91%) rename {Iceshrimp.Backend/Controllers => Iceshrimp.Shared}/Schemas/AuthResponse.cs (93%) rename {Iceshrimp.Backend/Controllers => Iceshrimp.Shared}/Schemas/ErrorResponse.cs (93%) rename {Iceshrimp.Backend/Controllers => Iceshrimp.Shared}/Schemas/InviteResponse.cs (76%) rename {Iceshrimp.Backend/Controllers => Iceshrimp.Shared}/Schemas/NoteCreateRequest.cs (88%) rename {Iceshrimp.Backend/Controllers => Iceshrimp.Shared}/Schemas/NoteResponse.cs (97%) rename {Iceshrimp.Backend/Controllers => Iceshrimp.Shared}/Schemas/NotificationResponse.cs (91%) create mode 100644 Iceshrimp.Shared/Schemas/UserProfileResponse.cs rename {Iceshrimp.Backend/Controllers => Iceshrimp.Shared}/Schemas/UserResponse.cs (92%) rename {Iceshrimp.Backend/Controllers => Iceshrimp.Shared}/Schemas/ValueResponse.cs (77%) diff --git a/Iceshrimp.Backend/Controllers/AdminController.cs b/Iceshrimp.Backend/Controllers/AdminController.cs index af2f2175..ef82b6c8 100644 --- a/Iceshrimp.Backend/Controllers/AdminController.cs +++ b/Iceshrimp.Backend/Controllers/AdminController.cs @@ -2,7 +2,7 @@ using System.Diagnostics.CodeAnalysis; using System.Net.Mime; using Iceshrimp.Backend.Controllers.Attributes; using Iceshrimp.Backend.Controllers.Federation; -using Iceshrimp.Backend.Controllers.Schemas; +using Iceshrimp.Shared.Schemas; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Extensions; diff --git a/Iceshrimp.Backend/Controllers/AuthController.cs b/Iceshrimp.Backend/Controllers/AuthController.cs index f19acdc3..11edec7e 100644 --- a/Iceshrimp.Backend/Controllers/AuthController.cs +++ b/Iceshrimp.Backend/Controllers/AuthController.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Net.Mime; using Iceshrimp.Backend.Controllers.Renderers; -using Iceshrimp.Backend.Controllers.Schemas; +using Iceshrimp.Shared.Schemas; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Helpers; diff --git a/Iceshrimp.Backend/Controllers/FallbackController.cs b/Iceshrimp.Backend/Controllers/FallbackController.cs index ec3d1140..84d90e69 100644 --- a/Iceshrimp.Backend/Controllers/FallbackController.cs +++ b/Iceshrimp.Backend/Controllers/FallbackController.cs @@ -1,6 +1,6 @@ using System.Net; using System.Net.Mime; -using Iceshrimp.Backend.Controllers.Schemas; +using Iceshrimp.Shared.Schemas; using Iceshrimp.Backend.Core.Middleware; using Microsoft.AspNetCore.Mvc; diff --git a/Iceshrimp.Backend/Controllers/Federation/ActivityPubController.cs b/Iceshrimp.Backend/Controllers/Federation/ActivityPubController.cs index 6896092e..9d2d92bd 100644 --- a/Iceshrimp.Backend/Controllers/Federation/ActivityPubController.cs +++ b/Iceshrimp.Backend/Controllers/Federation/ActivityPubController.cs @@ -1,7 +1,7 @@ using System.Text; using Iceshrimp.Backend.Controllers.Attributes; using Iceshrimp.Backend.Controllers.Federation.Attributes; -using Iceshrimp.Backend.Controllers.Schemas; +using Iceshrimp.Shared.Schemas; using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Extensions; diff --git a/Iceshrimp.Backend/Controllers/Federation/WellKnownController.cs b/Iceshrimp.Backend/Controllers/Federation/WellKnownController.cs index a89317fd..c0525b43 100644 --- a/Iceshrimp.Backend/Controllers/Federation/WellKnownController.cs +++ b/Iceshrimp.Backend/Controllers/Federation/WellKnownController.cs @@ -4,7 +4,7 @@ using System.Text; using System.Xml.Serialization; using Iceshrimp.Backend.Controllers.Federation.Attributes; using Iceshrimp.Backend.Controllers.Federation.Schemas; -using Iceshrimp.Backend.Controllers.Schemas; +using Iceshrimp.Shared.Schemas; using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database.Tables; diff --git a/Iceshrimp.Backend/Controllers/NoteController.cs b/Iceshrimp.Backend/Controllers/NoteController.cs index 23cfdbd2..04ce53fa 100644 --- a/Iceshrimp.Backend/Controllers/NoteController.cs +++ b/Iceshrimp.Backend/Controllers/NoteController.cs @@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations; using System.Net.Mime; using Iceshrimp.Backend.Controllers.Attributes; using Iceshrimp.Backend.Controllers.Renderers; -using Iceshrimp.Backend.Controllers.Schemas; +using Iceshrimp.Shared.Schemas; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Extensions; @@ -74,7 +74,7 @@ public class NoteController( [HttpGet("{id}/descendants")] [Authenticate] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(NoteResponse))] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable))] [ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(ErrorResponse))] public async Task GetNoteDescendants( string id, [FromQuery] [DefaultValue(20)] [Range(1, 100)] int? depth diff --git a/Iceshrimp.Backend/Controllers/NotificationController.cs b/Iceshrimp.Backend/Controllers/NotificationController.cs index 37111198..8c642a4d 100644 --- a/Iceshrimp.Backend/Controllers/NotificationController.cs +++ b/Iceshrimp.Backend/Controllers/NotificationController.cs @@ -2,6 +2,7 @@ using System.Net.Mime; using Iceshrimp.Backend.Controllers.Attributes; using Iceshrimp.Backend.Controllers.Renderers; using Iceshrimp.Backend.Controllers.Schemas; +using Iceshrimp.Shared.Schemas; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Extensions; using Iceshrimp.Backend.Core.Middleware; diff --git a/Iceshrimp.Backend/Controllers/Renderers/NoteRenderer.cs b/Iceshrimp.Backend/Controllers/Renderers/NoteRenderer.cs index 5f4c9fcb..9688eac8 100644 --- a/Iceshrimp.Backend/Controllers/Renderers/NoteRenderer.cs +++ b/Iceshrimp.Backend/Controllers/Renderers/NoteRenderer.cs @@ -1,4 +1,4 @@ - using Iceshrimp.Backend.Controllers.Schemas; + using Iceshrimp.Shared.Schemas; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Extensions; diff --git a/Iceshrimp.Backend/Controllers/Renderers/NotificationRenderer.cs b/Iceshrimp.Backend/Controllers/Renderers/NotificationRenderer.cs index 3a0d798f..7ef8b822 100644 --- a/Iceshrimp.Backend/Controllers/Renderers/NotificationRenderer.cs +++ b/Iceshrimp.Backend/Controllers/Renderers/NotificationRenderer.cs @@ -1,4 +1,4 @@ -using Iceshrimp.Backend.Controllers.Schemas; +using Iceshrimp.Shared.Schemas; using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Extensions; diff --git a/Iceshrimp.Backend/Controllers/Renderers/UserProfileRenderer.cs b/Iceshrimp.Backend/Controllers/Renderers/UserProfileRenderer.cs index 7d0d8e53..9b75c85d 100644 --- a/Iceshrimp.Backend/Controllers/Renderers/UserProfileRenderer.cs +++ b/Iceshrimp.Backend/Controllers/Renderers/UserProfileRenderer.cs @@ -1,4 +1,4 @@ -using Iceshrimp.Backend.Controllers.Schemas; +using Iceshrimp.Shared.Schemas; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Extensions; @@ -28,12 +28,19 @@ public class UserProfileRenderer(DatabaseContext db) _ => throw new ArgumentOutOfRangeException() }; + var fields = user.UserProfile?.Fields.Select(p => new UserProfileField + { + Name = p.Name, + Value = p.Value, + IsVerified = p.IsVerified + }); + return new UserProfileResponse { Id = user.Id, Bio = user.UserProfile?.Description, Birthday = user.UserProfile?.Birthday, - Fields = user.UserProfile?.Fields, + Fields = fields?.ToList(), Location = user.UserProfile?.Location, Followers = followers, Following = following diff --git a/Iceshrimp.Backend/Controllers/Renderers/UserRenderer.cs b/Iceshrimp.Backend/Controllers/Renderers/UserRenderer.cs index e9c5a8b2..f7fbc26b 100644 --- a/Iceshrimp.Backend/Controllers/Renderers/UserRenderer.cs +++ b/Iceshrimp.Backend/Controllers/Renderers/UserRenderer.cs @@ -1,4 +1,4 @@ -using Iceshrimp.Backend.Controllers.Schemas; +using Iceshrimp.Shared.Schemas; using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database.Tables; diff --git a/Iceshrimp.Backend/Controllers/Schemas/UserProfileResponse.cs b/Iceshrimp.Backend/Controllers/Schemas/UserProfileResponse.cs deleted file mode 100644 index 8d3a6e7b..00000000 --- a/Iceshrimp.Backend/Controllers/Schemas/UserProfileResponse.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Iceshrimp.Backend.Core.Database.Tables; -using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; - -namespace Iceshrimp.Backend.Controllers.Schemas; - -public class UserProfileResponse -{ - [J("id")] public required string Id { get; set; } - [J("birthday")] public required string? Birthday { get; set; } - [J("location")] public required string? Location { get; set; } - [J("fields")] public required UserProfile.Field[]? Fields { get; set; } - [J("bio")] public required string? Bio { get; set; } - [J("followers")] public required int? Followers { get; set; } - [J("following")] public required int? Following { get; set; } -} \ No newline at end of file diff --git a/Iceshrimp.Backend/Controllers/TimelineController.cs b/Iceshrimp.Backend/Controllers/TimelineController.cs index 3f8df480..dd2ddba4 100644 --- a/Iceshrimp.Backend/Controllers/TimelineController.cs +++ b/Iceshrimp.Backend/Controllers/TimelineController.cs @@ -2,6 +2,7 @@ using System.Net.Mime; using Iceshrimp.Backend.Controllers.Attributes; using Iceshrimp.Backend.Controllers.Renderers; using Iceshrimp.Backend.Controllers.Schemas; +using Iceshrimp.Shared.Schemas; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Extensions; using Iceshrimp.Backend.Core.Middleware; diff --git a/Iceshrimp.Backend/Controllers/UserController.cs b/Iceshrimp.Backend/Controllers/UserController.cs index fdf1bda7..2b1cd2eb 100644 --- a/Iceshrimp.Backend/Controllers/UserController.cs +++ b/Iceshrimp.Backend/Controllers/UserController.cs @@ -2,6 +2,7 @@ using System.Net.Mime; using Iceshrimp.Backend.Controllers.Attributes; using Iceshrimp.Backend.Controllers.Renderers; using Iceshrimp.Backend.Controllers.Schemas; +using Iceshrimp.Shared.Schemas; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Extensions; using Iceshrimp.Backend.Core.Middleware; diff --git a/Iceshrimp.Backend/Core/Database/Tables/UserProfile.cs b/Iceshrimp.Backend/Core/Database/Tables/UserProfile.cs index 88ff3ee5..8a21971c 100644 --- a/Iceshrimp.Backend/Core/Database/Tables/UserProfile.cs +++ b/Iceshrimp.Backend/Core/Database/Tables/UserProfile.cs @@ -180,8 +180,8 @@ public class UserProfile public class Field { - [J("name")] public required string Name { get; set; } - [J("value")] public required string Value { get; set; } - [J("verified")] public bool? IsVerified { get; set; } + public required string Name { get; set; } + public required string Value { get; set; } + public bool? IsVerified { get; set; } } } \ No newline at end of file diff --git a/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs b/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs index 444def9a..92e6cd98 100644 --- a/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs +++ b/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs @@ -5,6 +5,7 @@ using Iceshrimp.Backend.Controllers.Mastodon.Renderers; using Iceshrimp.Backend.Controllers.Mastodon.Schemas; using Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities; using Iceshrimp.Backend.Controllers.Schemas; +using Iceshrimp.Shared.Schemas; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Middleware; diff --git a/Iceshrimp.Backend/Core/Extensions/ServiceExtensions.cs b/Iceshrimp.Backend/Core/Extensions/ServiceExtensions.cs index 49b8d2ff..2bf5da4b 100644 --- a/Iceshrimp.Backend/Core/Extensions/ServiceExtensions.cs +++ b/Iceshrimp.Backend/Core/Extensions/ServiceExtensions.cs @@ -2,7 +2,7 @@ using System.Threading.RateLimiting; using Iceshrimp.Backend.Controllers.Federation; using Iceshrimp.Backend.Controllers.Mastodon.Renderers; using Iceshrimp.Backend.Controllers.Renderers; -using Iceshrimp.Backend.Controllers.Schemas; +using Iceshrimp.Shared.Schemas; using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Federation.WebFinger; diff --git a/Iceshrimp.Backend/Core/Middleware/ErrorHandlerMiddleware.cs b/Iceshrimp.Backend/Core/Middleware/ErrorHandlerMiddleware.cs index d30c92a2..94413e5a 100644 --- a/Iceshrimp.Backend/Core/Middleware/ErrorHandlerMiddleware.cs +++ b/Iceshrimp.Backend/Core/Middleware/ErrorHandlerMiddleware.cs @@ -2,7 +2,7 @@ using System.Diagnostics.CodeAnalysis; using System.Net; using Iceshrimp.Backend.Controllers.Mastodon.Attributes; using Iceshrimp.Backend.Controllers.Mastodon.Schemas; -using Iceshrimp.Backend.Controllers.Schemas; +using Iceshrimp.Shared.Schemas; using Iceshrimp.Backend.Core.Configuration; using Microsoft.Extensions.Options; diff --git a/Iceshrimp.Backend/Hubs/ExampleHub.cs b/Iceshrimp.Backend/Hubs/ExampleHub.cs new file mode 100644 index 00000000..2fc057b4 --- /dev/null +++ b/Iceshrimp.Backend/Hubs/ExampleHub.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.SignalR; +namespace Iceshrimp.Backend.Hubs; + +public class ExampleHub : Hub +{ + public async Task SendMessage(string user, string message) + { + await Clients.All.SendAsync("ReceiveMessage", user, message); + } +} \ No newline at end of file diff --git a/Iceshrimp.Backend/Iceshrimp.Backend.csproj b/Iceshrimp.Backend/Iceshrimp.Backend.csproj index 7a4a1791..02fc8475 100644 --- a/Iceshrimp.Backend/Iceshrimp.Backend.csproj +++ b/Iceshrimp.Backend/Iceshrimp.Backend.csproj @@ -33,6 +33,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -59,6 +60,7 @@ + diff --git a/Iceshrimp.Backend/Startup.cs b/Iceshrimp.Backend/Startup.cs index 41fc6232..a51d728e 100644 --- a/Iceshrimp.Backend/Startup.cs +++ b/Iceshrimp.Backend/Startup.cs @@ -1,4 +1,5 @@ using Iceshrimp.Backend.Core.Extensions; +using Iceshrimp.Backend.Hubs; var builder = WebApplication.CreateBuilder(args); @@ -16,6 +17,8 @@ builder.Services.AddLogging(logging => logging.AddCustomConsoleFormatter()); builder.Services.AddDatabaseContext(builder.Configuration); builder.Services.AddSlidingWindowRateLimiter(); builder.Services.AddCorsPolicies(); +builder.Services.AddSignalR().AddMessagePackProtocol(); +builder.Services.AddResponseCompression(); if (builder.Environment.IsDevelopment()) builder.Services.AddRazorPages().AddRazorRuntimeCompilation(); @@ -31,6 +34,9 @@ var app = builder.Build(); var config = await app.Initialize(args); // This determines the order of middleware execution in the request pipeline +if (!app.Environment.IsDevelopment()) + app.UseResponseCompression(); + app.UseRouting(); app.UseSwaggerWithOptions(); app.UseBlazorFrameworkFiles(); @@ -43,6 +49,7 @@ app.UseCustomMiddleware(); app.MapControllers(); app.MapFallbackToController("/api/{**slug}", "FallbackAction", "Fallback"); +app.MapHub("/hubs/example"); app.MapRazorPages(); app.MapFallbackToPage("/Shared/FrontendSPA"); diff --git a/Iceshrimp.Frontend/Core/ControllerModels/NoteControllerModel.cs b/Iceshrimp.Frontend/Core/ControllerModels/NoteControllerModel.cs new file mode 100644 index 00000000..573af15a --- /dev/null +++ b/Iceshrimp.Frontend/Core/ControllerModels/NoteControllerModel.cs @@ -0,0 +1,26 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Iceshrimp.Frontend.Core.Extensions; +using Iceshrimp.Shared.Schemas; +using Microsoft.AspNetCore.Http; + +namespace Iceshrimp.Frontend.Core.ControllerModels; + +internal class NoteControllerModel(HttpClient api) +{ + public Task GetNote(string id) => api.CallNullable(HttpMethod.Get, $"/note/{id}"); + + public Task GetNoteAscendants(string id, [DefaultValue(20)] [Range(1, 100)] int? limit) + { + var query = new QueryString(); + if (limit.HasValue) query.Add("limit", limit.ToString()); + return api.CallNullable(HttpMethod.Get, $"/note/{id}/ascendants", query); + } + + public Task GetNoteDescendants(string id, [DefaultValue(20)] [Range(1, 100)] int? depth) + { + var query = new QueryString(); + if (depth.HasValue) query.Add("depth", depth.ToString()); + return api.CallNullable(HttpMethod.Get, $"/note/{id}/descendants", query); + } +} \ No newline at end of file diff --git a/Iceshrimp.Frontend/Core/Extensions/HttpClientExtensions.cs b/Iceshrimp.Frontend/Core/Extensions/HttpClientExtensions.cs new file mode 100644 index 00000000..85160e98 --- /dev/null +++ b/Iceshrimp.Frontend/Core/Extensions/HttpClientExtensions.cs @@ -0,0 +1,59 @@ +using System.Net.Http.Json; +using System.Net.Mime; +using System.Text; +using Iceshrimp.Frontend.Core.Miscellaneous; +using Iceshrimp.Shared.Schemas; +using Microsoft.AspNetCore.Http; + +namespace Iceshrimp.Frontend.Core.Extensions; + +internal static class HttpClientExtensions +{ + public static async Task Call( + this HttpClient client, HttpMethod method, string path, QueryString? query = null, string? body = null + ) where T : class + { + var res = await CallInternal(client, method, path, query, body); + if (res.result != null) + return res.result; + throw new ApiException(res.error ?? throw new Exception("Deserialized API error was null")); + } + + public static async Task CallNullable( + this HttpClient client, HttpMethod method, string path, QueryString? query = null, string? body = null + ) where T : class + { + var res = await CallInternal(client, method, path, query, body); + if (res.result != null) + return res.result; + + var err = res.error ?? throw new Exception("Deserialized API error was null"); + if (err.StatusCode == 404) + return null; + + throw new ApiException(err); + } + + private static async Task<(T? result, ErrorResponse? error)> CallInternal( + this HttpClient client, HttpMethod method, string path, QueryString? query = null, string? body = null + ) where T : class + { + var request = new HttpRequestMessage(method, "/api/iceshrimp/" + path.TrimStart('/') + query); + request.Headers.Accept.ParseAdd(MediaTypeNames.Application.Json); + if (body != null) request.Content = new StringContent(body, Encoding.UTF8, MediaTypeNames.Application.Json); + + var res = await client.SendAsync(request); + if (res.IsSuccessStatusCode) + { + var deserialized = await res.Content.ReadFromJsonAsync(); + if (deserialized == null) + throw new Exception("Deserialized API response was null"); + return (deserialized, null); + } + + var error = await res.Content.ReadFromJsonAsync(); + if (error == null) + throw new Exception("Deserialized API error was null"); + return (null, error); + } +} \ No newline at end of file diff --git a/Iceshrimp.Frontend/Core/Extensions/HttpResponseMessageExtensions.cs b/Iceshrimp.Frontend/Core/Extensions/HttpResponseMessageExtensions.cs new file mode 100644 index 00000000..fa1a3acb --- /dev/null +++ b/Iceshrimp.Frontend/Core/Extensions/HttpResponseMessageExtensions.cs @@ -0,0 +1,6 @@ +namespace Iceshrimp.Frontend.Core.Extensions; + +public class HttpResponseMessageExtensions +{ + +} \ No newline at end of file diff --git a/Iceshrimp.Frontend/Core/Miscellaneous/ApiException.cs b/Iceshrimp.Frontend/Core/Miscellaneous/ApiException.cs new file mode 100644 index 00000000..125a804f --- /dev/null +++ b/Iceshrimp.Frontend/Core/Miscellaneous/ApiException.cs @@ -0,0 +1,8 @@ +using Iceshrimp.Shared.Schemas; + +namespace Iceshrimp.Frontend.Core.Miscellaneous; + +internal class ApiException(ErrorResponse error) : Exception +{ + public ErrorResponse Response => error; +} \ No newline at end of file diff --git a/Iceshrimp.Frontend/Core/Services/ApiService.cs b/Iceshrimp.Frontend/Core/Services/ApiService.cs new file mode 100644 index 00000000..e8ef81d7 --- /dev/null +++ b/Iceshrimp.Frontend/Core/Services/ApiService.cs @@ -0,0 +1,8 @@ +using Iceshrimp.Frontend.Core.ControllerModels; + +namespace Iceshrimp.Frontend.Core.Services; + +internal class ApiService(HttpClient client) +{ + internal NoteControllerModel Notes = new(client); +} \ No newline at end of file diff --git a/Iceshrimp.Frontend/Iceshrimp.Frontend.csproj b/Iceshrimp.Frontend/Iceshrimp.Frontend.csproj index c9ab3cf9..6d4a5a9c 100644 --- a/Iceshrimp.Frontend/Iceshrimp.Frontend.csproj +++ b/Iceshrimp.Frontend/Iceshrimp.Frontend.csproj @@ -5,6 +5,7 @@ enable enable true + true @@ -13,6 +14,9 @@ + + + @@ -24,4 +28,9 @@ <_ContentIncludedByDefault Remove="wwwroot\assets\transparent.png" /> + + + + + diff --git a/Iceshrimp.Frontend/Pages/Counter.razor b/Iceshrimp.Frontend/Pages/Counter.razor index d66f6970..70d4668c 100644 --- a/Iceshrimp.Frontend/Pages/Counter.razor +++ b/Iceshrimp.Frontend/Pages/Counter.razor @@ -4,7 +4,7 @@

Counter

-

Current count: @_currentCount

+

Current count (edited): @_currentCount

diff --git a/Iceshrimp.Frontend/Pages/Hub.razor b/Iceshrimp.Frontend/Pages/Hub.razor new file mode 100644 index 00000000..0bb6dd87 --- /dev/null +++ b/Iceshrimp.Frontend/Pages/Hub.razor @@ -0,0 +1,72 @@ +@page "/hub" +@using Microsoft.AspNetCore.SignalR.Client +@inject NavigationManager Navigation +@implements IAsyncDisposable + +Home + +
+ +
+
+ +
+ + +
+ +
    + @foreach (var message in messages) + { +
  • @message
  • + } +
+ +@code { + private HubConnection? hubConnection; + private List messages = []; + private string? userInput; + private string? messageInput; + + protected override async Task OnInitializedAsync() + { + hubConnection = new HubConnectionBuilder() + .WithUrl(Navigation.ToAbsoluteUri("/hubs/example")) + .AddMessagePackProtocol() + .Build(); + + hubConnection.On("ReceiveMessage", (user, message) => + { + var encodedMsg = $"{user}: {message}"; + messages.Add(encodedMsg); + InvokeAsync(StateHasChanged); + }); + + await hubConnection.StartAsync(); + } + + private async Task Send() + { + if (hubConnection is not null) + { + await hubConnection.SendAsync("SendMessage", userInput, messageInput); + } + } + + public bool IsConnected => + hubConnection?.State == HubConnectionState.Connected; + + public async ValueTask DisposeAsync() + { + if (hubConnection is not null) + { + await hubConnection.DisposeAsync(); + } + } +} \ No newline at end of file diff --git a/Iceshrimp.Frontend/Pages/TestNote.razor b/Iceshrimp.Frontend/Pages/TestNote.razor new file mode 100644 index 00000000..a6286048 --- /dev/null +++ b/Iceshrimp.Frontend/Pages/TestNote.razor @@ -0,0 +1,44 @@ +@page "/TestNote/{id}" +@using Iceshrimp.Frontend.Core.Miscellaneous +@using Iceshrimp.Frontend.Core.Services +@using Iceshrimp.Shared.Schemas +@inject ApiService Api +

TestNote

+ +@if (_note != null) +{ +

@_note.Text

+} +else if (_error != null) +{ +

Error!: @_error.Error

+} +else +{ +

Fetching note...

+} + +@code { + [Parameter] + public string Id { get; set; } = null!; + + private ErrorResponse? _error; + private NoteResponse? _note; + + protected override async Task OnInitializedAsync() + { + try + { + _note = await Api.Notes.GetNote(Id) ?? throw new ApiException(new ErrorResponse + { + StatusCode = 404, + Error = "Note not found", + RequestId = "-" + }); + } + catch (ApiException e) + { + _error = e.Response; + } + } +} \ No newline at end of file diff --git a/Iceshrimp.Frontend/Program.cs b/Iceshrimp.Frontend/Program.cs index edd29dc3..491fcad6 100644 --- a/Iceshrimp.Frontend/Program.cs +++ b/Iceshrimp.Frontend/Program.cs @@ -1,11 +1,13 @@ using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Iceshrimp.Frontend; +using Iceshrimp.Frontend.Core.Services; var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("#app"); builder.RootComponents.Add("head::after"); builder.Services.AddScoped(_ => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); +builder.Services.AddScoped(); await builder.Build().RunAsync(); \ No newline at end of file diff --git a/Iceshrimp.NET.sln b/Iceshrimp.NET.sln index 344e22cb..bb20a25e 100644 --- a/Iceshrimp.NET.sln +++ b/Iceshrimp.NET.sln @@ -8,6 +8,8 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Iceshrimp.Parsing", "Iceshr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Iceshrimp.Frontend", "Iceshrimp.Frontend\Iceshrimp.Frontend.csproj", "{8BAF3DEB-19A7-4044-A3F3-75C8B9B51863}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Iceshrimp.Shared", "Iceshrimp.Shared\Iceshrimp.Shared.csproj", "{25E8E423-D2F7-437B-8E9B-5277BA5CE3CD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -30,5 +32,9 @@ Global {8BAF3DEB-19A7-4044-A3F3-75C8B9B51863}.Debug|Any CPU.Build.0 = Debug|Any CPU {8BAF3DEB-19A7-4044-A3F3-75C8B9B51863}.Release|Any CPU.ActiveCfg = Release|Any CPU {8BAF3DEB-19A7-4044-A3F3-75C8B9B51863}.Release|Any CPU.Build.0 = Release|Any CPU + {25E8E423-D2F7-437B-8E9B-5277BA5CE3CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {25E8E423-D2F7-437B-8E9B-5277BA5CE3CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {25E8E423-D2F7-437B-8E9B-5277BA5CE3CD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {25E8E423-D2F7-437B-8E9B-5277BA5CE3CD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Iceshrimp.Shared/Iceshrimp.Shared.csproj b/Iceshrimp.Shared/Iceshrimp.Shared.csproj new file mode 100644 index 00000000..3a635329 --- /dev/null +++ b/Iceshrimp.Shared/Iceshrimp.Shared.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/Iceshrimp.Backend/Controllers/Schemas/AuthRequest.cs b/Iceshrimp.Shared/Schemas/AuthRequest.cs similarity index 91% rename from Iceshrimp.Backend/Controllers/Schemas/AuthRequest.cs rename to Iceshrimp.Shared/Schemas/AuthRequest.cs index f6919287..35b82658 100644 --- a/Iceshrimp.Backend/Controllers/Schemas/AuthRequest.cs +++ b/Iceshrimp.Shared/Schemas/AuthRequest.cs @@ -1,6 +1,6 @@ using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; -namespace Iceshrimp.Backend.Controllers.Schemas; +namespace Iceshrimp.Shared.Schemas; public class AuthRequest { diff --git a/Iceshrimp.Backend/Controllers/Schemas/AuthResponse.cs b/Iceshrimp.Shared/Schemas/AuthResponse.cs similarity index 93% rename from Iceshrimp.Backend/Controllers/Schemas/AuthResponse.cs rename to Iceshrimp.Shared/Schemas/AuthResponse.cs index 6e2f78ba..ec11bdd9 100644 --- a/Iceshrimp.Backend/Controllers/Schemas/AuthResponse.cs +++ b/Iceshrimp.Shared/Schemas/AuthResponse.cs @@ -3,7 +3,7 @@ using System.Text.Json.Serialization; using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; using JE = System.Runtime.Serialization.EnumMemberAttribute; -namespace Iceshrimp.Backend.Controllers.Schemas; +namespace Iceshrimp.Shared.Schemas; public class AuthStatusConverter() : JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseLower); diff --git a/Iceshrimp.Backend/Controllers/Schemas/ErrorResponse.cs b/Iceshrimp.Shared/Schemas/ErrorResponse.cs similarity index 93% rename from Iceshrimp.Backend/Controllers/Schemas/ErrorResponse.cs rename to Iceshrimp.Shared/Schemas/ErrorResponse.cs index 781bffa3..d1fe69e4 100644 --- a/Iceshrimp.Backend/Controllers/Schemas/ErrorResponse.cs +++ b/Iceshrimp.Shared/Schemas/ErrorResponse.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; using JI = System.Text.Json.Serialization.JsonIgnoreAttribute; -namespace Iceshrimp.Backend.Controllers.Schemas; +namespace Iceshrimp.Shared.Schemas; public class ErrorResponse { diff --git a/Iceshrimp.Backend/Controllers/Schemas/InviteResponse.cs b/Iceshrimp.Shared/Schemas/InviteResponse.cs similarity index 76% rename from Iceshrimp.Backend/Controllers/Schemas/InviteResponse.cs rename to Iceshrimp.Shared/Schemas/InviteResponse.cs index b81d27e4..5b3db508 100644 --- a/Iceshrimp.Backend/Controllers/Schemas/InviteResponse.cs +++ b/Iceshrimp.Shared/Schemas/InviteResponse.cs @@ -1,6 +1,6 @@ using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; -namespace Iceshrimp.Backend.Controllers.Schemas; +namespace Iceshrimp.Shared.Schemas; public class InviteResponse { diff --git a/Iceshrimp.Backend/Controllers/Schemas/NoteCreateRequest.cs b/Iceshrimp.Shared/Schemas/NoteCreateRequest.cs similarity index 88% rename from Iceshrimp.Backend/Controllers/Schemas/NoteCreateRequest.cs rename to Iceshrimp.Shared/Schemas/NoteCreateRequest.cs index b89a1bdc..c2d1390d 100644 --- a/Iceshrimp.Backend/Controllers/Schemas/NoteCreateRequest.cs +++ b/Iceshrimp.Shared/Schemas/NoteCreateRequest.cs @@ -1,6 +1,6 @@ using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; -namespace Iceshrimp.Backend.Controllers.Schemas; +namespace Iceshrimp.Shared.Schemas; public class NoteCreateRequest { diff --git a/Iceshrimp.Backend/Controllers/Schemas/NoteResponse.cs b/Iceshrimp.Shared/Schemas/NoteResponse.cs similarity index 97% rename from Iceshrimp.Backend/Controllers/Schemas/NoteResponse.cs rename to Iceshrimp.Shared/Schemas/NoteResponse.cs index bc649ee9..445073fe 100644 --- a/Iceshrimp.Backend/Controllers/Schemas/NoteResponse.cs +++ b/Iceshrimp.Shared/Schemas/NoteResponse.cs @@ -1,7 +1,7 @@ using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; using JI = System.Text.Json.Serialization.JsonIgnoreAttribute; -namespace Iceshrimp.Backend.Controllers.Schemas; +namespace Iceshrimp.Shared.Schemas; public class NoteResponse : NoteWithQuote { diff --git a/Iceshrimp.Backend/Controllers/Schemas/NotificationResponse.cs b/Iceshrimp.Shared/Schemas/NotificationResponse.cs similarity index 91% rename from Iceshrimp.Backend/Controllers/Schemas/NotificationResponse.cs rename to Iceshrimp.Shared/Schemas/NotificationResponse.cs index c60f06cf..780f7749 100644 --- a/Iceshrimp.Backend/Controllers/Schemas/NotificationResponse.cs +++ b/Iceshrimp.Shared/Schemas/NotificationResponse.cs @@ -1,6 +1,6 @@ using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; -namespace Iceshrimp.Backend.Controllers.Schemas; +namespace Iceshrimp.Shared.Schemas; public class NotificationResponse { diff --git a/Iceshrimp.Shared/Schemas/UserProfileResponse.cs b/Iceshrimp.Shared/Schemas/UserProfileResponse.cs new file mode 100644 index 00000000..289048cc --- /dev/null +++ b/Iceshrimp.Shared/Schemas/UserProfileResponse.cs @@ -0,0 +1,21 @@ +using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; + +namespace Iceshrimp.Shared.Schemas; + +public class UserProfileResponse +{ + [J("id")] public required string Id { get; set; } + [J("birthday")] public required string? Birthday { get; set; } + [J("location")] public required string? Location { get; set; } + [J("fields")] public required List? Fields { get; set; } + [J("bio")] public required string? Bio { get; set; } + [J("followers")] public required int? Followers { get; set; } + [J("following")] public required int? Following { get; set; } +} + +public class UserProfileField +{ + [J("name")] public required string Name { get; set; } + [J("value")] public required string Value { get; set; } + [J("verified")] public bool? IsVerified { get; set; } +} \ No newline at end of file diff --git a/Iceshrimp.Backend/Controllers/Schemas/UserResponse.cs b/Iceshrimp.Shared/Schemas/UserResponse.cs similarity index 92% rename from Iceshrimp.Backend/Controllers/Schemas/UserResponse.cs rename to Iceshrimp.Shared/Schemas/UserResponse.cs index e6acf814..630bc55a 100644 --- a/Iceshrimp.Backend/Controllers/Schemas/UserResponse.cs +++ b/Iceshrimp.Shared/Schemas/UserResponse.cs @@ -1,6 +1,6 @@ using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; -namespace Iceshrimp.Backend.Controllers.Schemas; +namespace Iceshrimp.Shared.Schemas; public class UserResponse { diff --git a/Iceshrimp.Backend/Controllers/Schemas/ValueResponse.cs b/Iceshrimp.Shared/Schemas/ValueResponse.cs similarity index 77% rename from Iceshrimp.Backend/Controllers/Schemas/ValueResponse.cs rename to Iceshrimp.Shared/Schemas/ValueResponse.cs index f41f51be..276503b5 100644 --- a/Iceshrimp.Backend/Controllers/Schemas/ValueResponse.cs +++ b/Iceshrimp.Shared/Schemas/ValueResponse.cs @@ -1,6 +1,6 @@ using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; -namespace Iceshrimp.Backend.Controllers.Schemas; +namespace Iceshrimp.Shared.Schemas; public class ValueResponse(long count) {