[frontend] Bootstrap shared DTOs, API abstractions, SignalR & more
Some checks failed
/ unit-tests (push) Successful in 44s
/ build-and-push (push) Has been cancelled

This commit is contained in:
Laura Hausmann 2024-04-01 20:22:45 +02:00
parent 674b34a930
commit 6bc2b8d57c
Signed by: zotan
GPG key ID: D044E84C5BE01605
43 changed files with 327 additions and 42 deletions

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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<NoteResponse>))]
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(ErrorResponse))]
public async Task<IActionResult> GetNoteDescendants(
string id, [FromQuery] [DefaultValue(20)] [Range(1, 100)] int? depth

View file

@ -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;

View file

@ -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;

View file

@ -1,4 +1,4 @@
using Iceshrimp.Backend.Controllers.Schemas;
using Iceshrimp.Shared.Schemas;
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;

View file

@ -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

View file

@ -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;

View file

@ -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; }
}

View file

@ -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;

View file

@ -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;

View file

@ -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; }
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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);
}
}

View file

@ -33,6 +33,7 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.3" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.3" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@ -59,6 +60,7 @@
<ItemGroup>
<ProjectReference Include="..\Iceshrimp.Frontend\Iceshrimp.Frontend.csproj" />
<ProjectReference Include="..\Iceshrimp.Parsing\Iceshrimp.Parsing.fsproj"/>
<ProjectReference Include="..\Iceshrimp.Shared\Iceshrimp.Shared.csproj" />
</ItemGroup>
<ItemGroup>

View file

@ -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<ExampleHub>("/hubs/example");
app.MapRazorPages();
app.MapFallbackToPage("/Shared/FrontendSPA");

View file

@ -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<NoteResponse?> GetNote(string id) => api.CallNullable<NoteResponse>(HttpMethod.Get, $"/note/{id}");
public Task<NoteResponse?> 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<NoteResponse>(HttpMethod.Get, $"/note/{id}/ascendants", query);
}
public Task<NoteResponse?> 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<NoteResponse>(HttpMethod.Get, $"/note/{id}/descendants", query);
}
}

View file

@ -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<T> Call<T>(
this HttpClient client, HttpMethod method, string path, QueryString? query = null, string? body = null
) where T : class
{
var res = await CallInternal<T>(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<T?> CallNullable<T>(
this HttpClient client, HttpMethod method, string path, QueryString? query = null, string? body = null
) where T : class
{
var res = await CallInternal<T>(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<T>(
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<T>();
if (deserialized == null)
throw new Exception("Deserialized API response was null");
return (deserialized, null);
}
var error = await res.Content.ReadFromJsonAsync<ErrorResponse>();
if (error == null)
throw new Exception("Deserialized API error was null");
return (null, error);
}
}

View file

@ -0,0 +1,6 @@
namespace Iceshrimp.Frontend.Core.Extensions;
public class HttpResponseMessageExtensions
{
}

View file

@ -0,0 +1,8 @@
using Iceshrimp.Shared.Schemas;
namespace Iceshrimp.Frontend.Core.Miscellaneous;
internal class ApiException(ErrorResponse error) : Exception
{
public ErrorResponse Response => error;
}

View file

@ -0,0 +1,8 @@
using Iceshrimp.Frontend.Core.ControllerModels;
namespace Iceshrimp.Frontend.Core.Services;
internal class ApiService(HttpClient client)
{
internal NoteControllerModel Notes = new(client);
}

View file

@ -5,6 +5,7 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PublishTrimmed>true</PublishTrimmed>
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
</PropertyGroup>
<PropertyGroup Condition="'$(EnableAOT)' == 'true'">
@ -13,6 +14,9 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.3" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="8.0.3" />
</ItemGroup>
<ItemGroup>
@ -24,4 +28,9 @@
<_ContentIncludedByDefault Remove="wwwroot\assets\transparent.png" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Iceshrimp.Parsing\Iceshrimp.Parsing.fsproj" />
<ProjectReference Include="..\Iceshrimp.Shared\Iceshrimp.Shared.csproj" />
</ItemGroup>
</Project>

View file

@ -4,7 +4,7 @@
<h1>Counter</h1>
<p role="status">Current count: @_currentCount</p>
<p role="status">Current count (edited): @_currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

View file

@ -0,0 +1,72 @@
@page "/hub"
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager Navigation
@implements IAsyncDisposable
<PageTitle>Home</PageTitle>
<div class="form-group">
<label>
User:
<input @bind="userInput" />
</label>
</div>
<div class="form-group">
<label>
Message:
<input @bind="messageInput" size="50" />
</label>
</div>
<button @onclick="Send" disabled="@(!IsConnected)">Send</button>
<hr>
<ul id="messagesList">
@foreach (var message in messages)
{
<li>@message</li>
}
</ul>
@code {
private HubConnection? hubConnection;
private List<string> messages = [];
private string? userInput;
private string? messageInput;
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/hubs/example"))
.AddMessagePackProtocol()
.Build();
hubConnection.On<string, string>("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();
}
}
}

View file

@ -0,0 +1,44 @@
@page "/TestNote/{id}"
@using Iceshrimp.Frontend.Core.Miscellaneous
@using Iceshrimp.Frontend.Core.Services
@using Iceshrimp.Shared.Schemas
@inject ApiService Api
<h3>TestNote</h3>
@if (_note != null)
{
<p id="text">@_note.Text</p>
}
else if (_error != null)
{
<p>Error!: @_error.Error</p>
}
else
{
<p>Fetching note...</p>
}
@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;
}
}
}

View file

@ -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>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(_ => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddScoped<ApiService>();
await builder.Build().RunAsync();

View file

@ -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

View file

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View file

@ -1,6 +1,6 @@
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
namespace Iceshrimp.Backend.Controllers.Schemas;
namespace Iceshrimp.Shared.Schemas;
public class AuthRequest
{

View file

@ -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<AuthStatusEnum>(JsonNamingPolicy.SnakeCaseLower);

View file

@ -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
{

View file

@ -1,6 +1,6 @@
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
namespace Iceshrimp.Backend.Controllers.Schemas;
namespace Iceshrimp.Shared.Schemas;
public class InviteResponse
{

View file

@ -1,6 +1,6 @@
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
namespace Iceshrimp.Backend.Controllers.Schemas;
namespace Iceshrimp.Shared.Schemas;
public class NoteCreateRequest
{

View file

@ -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
{

View file

@ -1,6 +1,6 @@
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
namespace Iceshrimp.Backend.Controllers.Schemas;
namespace Iceshrimp.Shared.Schemas;
public class NotificationResponse
{

View file

@ -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<UserProfileField>? 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; }
}

View file

@ -1,6 +1,6 @@
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
namespace Iceshrimp.Backend.Controllers.Schemas;
namespace Iceshrimp.Shared.Schemas;
public class UserResponse
{

View file

@ -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)
{