Compare commits
	
		
			2 commits
		
	
	
		
			6c9d1c328b
			...
			fa3c1ccaa7
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| fa3c1ccaa7 | |||
| 22d09ad7a6 | 
					 10 changed files with 573 additions and 156 deletions
				
			
		|  | @ -20,15 +20,16 @@ public class UsersController( | ||||||
| { | { | ||||||
|     [HttpGet("{userRef}")] |     [HttpGet("{userRef}")] | ||||||
|     [ProducesResponseType<UserRendererService.UserResponse>(statusCode: StatusCodes.Status200OK)] |     [ProducesResponseType<UserRendererService.UserResponse>(statusCode: StatusCodes.Status200OK)] | ||||||
|     public async Task<IActionResult> GetUserAsync(string userRef) |     public async Task<IActionResult> GetUserAsync(string userRef, CancellationToken ct = default) | ||||||
|     { |     { | ||||||
|         var user = await db.ResolveUserAsync(userRef, CurrentToken); |         var user = await db.ResolveUserAsync(userRef, CurrentToken, ct); | ||||||
|         return Ok(await userRendererService.RenderUserAsync( |         return Ok(await userRendererService.RenderUserAsync( | ||||||
|             user, |             user, | ||||||
|             selfUser: CurrentUser, |             selfUser: CurrentUser, | ||||||
|             token: CurrentToken, |             token: CurrentToken, | ||||||
|             renderMembers: true, |             renderMembers: true, | ||||||
|             renderAuthMethods: true |             renderAuthMethods: true, | ||||||
|  |             ct: ct | ||||||
|         )); |         )); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -59,6 +60,11 @@ public class UsersController( | ||||||
|             user.Bio = req.Bio; |             user.Bio = req.Bio; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         if (req.HasProperty(nameof(req.Links))) | ||||||
|  |         { | ||||||
|  |             user.Links = req.Links ?? []; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         if (req.HasProperty(nameof(req.Avatar))) |         if (req.HasProperty(nameof(req.Avatar))) | ||||||
|             errors.Add(("avatar", ValidationUtils.ValidateAvatar(req.Avatar))); |             errors.Add(("avatar", ValidationUtils.ValidateAvatar(req.Avatar))); | ||||||
| 
 | 
 | ||||||
|  | @ -150,5 +156,37 @@ public class UsersController( | ||||||
|         public string? DisplayName { get; init; } |         public string? DisplayName { get; init; } | ||||||
|         public string? Bio { get; init; } |         public string? Bio { get; init; } | ||||||
|         public string? Avatar { get; init; } |         public string? Avatar { get; init; } | ||||||
|  |         public string[]? Links { get; init; } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |      | ||||||
|  |     [HttpGet("@me/settings")] | ||||||
|  |     [Authorize("user.read_hidden")] | ||||||
|  |     [ProducesResponseType<UserSettings>(statusCode: StatusCodes.Status200OK)] | ||||||
|  |     public async Task<IActionResult> GetUserSettingsAsync(CancellationToken ct = default) | ||||||
|  |     { | ||||||
|  |         var user = await db.Users.FirstAsync(u => u.Id == CurrentUser!.Id, ct); | ||||||
|  |         return Ok(user.Settings); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     [HttpPatch("@me/settings")] | ||||||
|  |     [Authorize("user.read_hidden", "user.update")] | ||||||
|  |     [ProducesResponseType<UserSettings>(statusCode: StatusCodes.Status200OK)] | ||||||
|  |     public async Task<IActionResult> UpdateUserSettingsAsync([FromBody] UpdateUserSettingsRequest req, CancellationToken ct = default) | ||||||
|  |     { | ||||||
|  |         var user = await db.Users.FirstAsync(u => u.Id == CurrentUser!.Id, ct); | ||||||
|  | 
 | ||||||
|  |         if (req.HasProperty(nameof(req.DarkMode))) | ||||||
|  |             user.Settings.DarkMode = req.DarkMode; | ||||||
|  | 
 | ||||||
|  |         db.Update(user); | ||||||
|  |         await db.SaveChangesAsync(ct); | ||||||
|  | 
 | ||||||
|  |         return Ok(user.Settings); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public class UpdateUserSettingsRequest : PatchRequest | ||||||
|  |     { | ||||||
|  |         public bool? DarkMode { get; init; } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -63,6 +63,7 @@ public class DatabaseContext : DbContext | ||||||
|         modelBuilder.Entity<User>().Property(u => u.Names).HasColumnType("jsonb"); |         modelBuilder.Entity<User>().Property(u => u.Names).HasColumnType("jsonb"); | ||||||
|         modelBuilder.Entity<User>().Property(u => u.Pronouns).HasColumnType("jsonb"); |         modelBuilder.Entity<User>().Property(u => u.Pronouns).HasColumnType("jsonb"); | ||||||
|         modelBuilder.Entity<User>().Property(u => u.CustomPreferences).HasColumnType("jsonb"); |         modelBuilder.Entity<User>().Property(u => u.CustomPreferences).HasColumnType("jsonb"); | ||||||
|  |         modelBuilder.Entity<User>().Property(u => u.Settings).HasColumnType("jsonb"); | ||||||
| 
 | 
 | ||||||
|         modelBuilder.Entity<Member>().Property(m => m.Fields).HasColumnType("jsonb"); |         modelBuilder.Entity<Member>().Property(m => m.Fields).HasColumnType("jsonb"); | ||||||
|         modelBuilder.Entity<Member>().Property(m => m.Names).HasColumnType("jsonb"); |         modelBuilder.Entity<Member>().Property(m => m.Names).HasColumnType("jsonb"); | ||||||
|  |  | ||||||
|  | @ -9,23 +9,29 @@ namespace Foxnouns.Backend.Database; | ||||||
| 
 | 
 | ||||||
| public static class DatabaseQueryExtensions | public static class DatabaseQueryExtensions | ||||||
| { | { | ||||||
|     public static async Task<User> ResolveUserAsync(this DatabaseContext context, string userRef, Token? token) |     public static async Task<User> ResolveUserAsync(this DatabaseContext context, string userRef, Token? token, | ||||||
|  |         CancellationToken ct = default) | ||||||
|     { |     { | ||||||
|         if (userRef == "@me" && token != null) |         if (userRef == "@me") | ||||||
|             return await context.Users.FirstAsync(u => u.Id == token.UserId); |         { | ||||||
|  |             return token != null | ||||||
|  |                 ? await context.Users.FirstAsync(u => u.Id == token.UserId, ct) | ||||||
|  |                 : throw new ApiError.Unauthorized("This endpoint requires an authenticated user.", | ||||||
|  |                     ErrorCode.AuthenticationRequired); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         User? user; |         User? user; | ||||||
|         if (Snowflake.TryParse(userRef, out var snowflake)) |         if (Snowflake.TryParse(userRef, out var snowflake)) | ||||||
|         { |         { | ||||||
|             user = await context.Users |             user = await context.Users | ||||||
|                 .Where(u => !u.Deleted) |                 .Where(u => !u.Deleted) | ||||||
|                 .FirstOrDefaultAsync(u => u.Id == snowflake); |                 .FirstOrDefaultAsync(u => u.Id == snowflake, ct); | ||||||
|             if (user != null) return user; |             if (user != null) return user; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         user = await context.Users |         user = await context.Users | ||||||
|             .Where(u => !u.Deleted) |             .Where(u => !u.Deleted) | ||||||
|             .FirstOrDefaultAsync(u => u.Username == userRef); |             .FirstOrDefaultAsync(u => u.Username == userRef, ct); | ||||||
|         if (user != null) return user; |         if (user != null) return user; | ||||||
|         throw new ApiError.NotFound("No user with that ID or username found.", code: ErrorCode.UserNotFound); |         throw new ApiError.NotFound("No user with that ID or username found.", code: ErrorCode.UserNotFound); | ||||||
|     } |     } | ||||||
|  |  | ||||||
							
								
								
									
										432
									
								
								Foxnouns.Backend/Database/Migrations/20240905191709_AddUserSettings.Designer.cs
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										432
									
								
								Foxnouns.Backend/Database/Migrations/20240905191709_AddUserSettings.Designer.cs
									
										
									
										generated
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,432 @@ | ||||||
|  | // <auto-generated /> | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using Foxnouns.Backend.Database; | ||||||
|  | using Foxnouns.Backend.Database.Models; | ||||||
|  | using Microsoft.EntityFrameworkCore; | ||||||
|  | using Microsoft.EntityFrameworkCore.Infrastructure; | ||||||
|  | using Microsoft.EntityFrameworkCore.Migrations; | ||||||
|  | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; | ||||||
|  | using NodaTime; | ||||||
|  | using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; | ||||||
|  | 
 | ||||||
|  | #nullable disable | ||||||
|  | 
 | ||||||
|  | namespace Foxnouns.Backend.Database.Migrations | ||||||
|  | { | ||||||
|  |     [DbContext(typeof(DatabaseContext))] | ||||||
|  |     [Migration("20240905191709_AddUserSettings")] | ||||||
|  |     partial class AddUserSettings | ||||||
|  |     { | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         protected override void BuildTargetModel(ModelBuilder modelBuilder) | ||||||
|  |         { | ||||||
|  | #pragma warning disable 612, 618 | ||||||
|  |             modelBuilder | ||||||
|  |                 .HasAnnotation("ProductVersion", "8.0.7") | ||||||
|  |                 .HasAnnotation("Relational:MaxIdentifierLength", 63); | ||||||
|  | 
 | ||||||
|  |             NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Foxnouns.Backend.Database.Models.Application", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<long>("Id") | ||||||
|  |                         .HasColumnType("bigint") | ||||||
|  |                         .HasColumnName("id"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ClientId") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("client_id"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ClientSecret") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("client_secret"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Name") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("name"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string[]>("RedirectUris") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("text[]") | ||||||
|  |                         .HasColumnName("redirect_uris"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string[]>("Scopes") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("text[]") | ||||||
|  |                         .HasColumnName("scopes"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("Id") | ||||||
|  |                         .HasName("pk_applications"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("applications", (string)null); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Foxnouns.Backend.Database.Models.AuthMethod", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<long>("Id") | ||||||
|  |                         .HasColumnType("bigint") | ||||||
|  |                         .HasColumnName("id"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<int>("AuthType") | ||||||
|  |                         .HasColumnType("integer") | ||||||
|  |                         .HasColumnName("auth_type"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<long?>("FediverseApplicationId") | ||||||
|  |                         .HasColumnType("bigint") | ||||||
|  |                         .HasColumnName("fediverse_application_id"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("RemoteId") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("remote_id"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("RemoteUsername") | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("remote_username"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<long>("UserId") | ||||||
|  |                         .HasColumnType("bigint") | ||||||
|  |                         .HasColumnName("user_id"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("Id") | ||||||
|  |                         .HasName("pk_auth_methods"); | ||||||
|  | 
 | ||||||
|  |                     b.HasIndex("FediverseApplicationId") | ||||||
|  |                         .HasDatabaseName("ix_auth_methods_fediverse_application_id"); | ||||||
|  | 
 | ||||||
|  |                     b.HasIndex("UserId") | ||||||
|  |                         .HasDatabaseName("ix_auth_methods_user_id"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("auth_methods", (string)null); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Foxnouns.Backend.Database.Models.FediverseApplication", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<long>("Id") | ||||||
|  |                         .HasColumnType("bigint") | ||||||
|  |                         .HasColumnName("id"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ClientId") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("client_id"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ClientSecret") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("client_secret"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Domain") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("domain"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<int>("InstanceType") | ||||||
|  |                         .HasColumnType("integer") | ||||||
|  |                         .HasColumnName("instance_type"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("Id") | ||||||
|  |                         .HasName("pk_fediverse_applications"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("fediverse_applications", (string)null); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Foxnouns.Backend.Database.Models.Member", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<long>("Id") | ||||||
|  |                         .HasColumnType("bigint") | ||||||
|  |                         .HasColumnName("id"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Avatar") | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("avatar"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Bio") | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("bio"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("DisplayName") | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("display_name"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<List<Field>>("Fields") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("jsonb") | ||||||
|  |                         .HasColumnName("fields"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string[]>("Links") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("text[]") | ||||||
|  |                         .HasColumnName("links"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Name") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("name"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<List<FieldEntry>>("Names") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("jsonb") | ||||||
|  |                         .HasColumnName("names"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<List<Pronoun>>("Pronouns") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("jsonb") | ||||||
|  |                         .HasColumnName("pronouns"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<bool>("Unlisted") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("unlisted"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<long>("UserId") | ||||||
|  |                         .HasColumnType("bigint") | ||||||
|  |                         .HasColumnName("user_id"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("Id") | ||||||
|  |                         .HasName("pk_members"); | ||||||
|  | 
 | ||||||
|  |                     b.HasIndex("UserId", "Name") | ||||||
|  |                         .IsUnique() | ||||||
|  |                         .HasDatabaseName("ix_members_user_id_name"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("members", (string)null); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Foxnouns.Backend.Database.Models.TemporaryKey", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<long>("Id") | ||||||
|  |                         .ValueGeneratedOnAdd() | ||||||
|  |                         .HasColumnType("bigint") | ||||||
|  |                         .HasColumnName("id"); | ||||||
|  | 
 | ||||||
|  |                     NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id")); | ||||||
|  | 
 | ||||||
|  |                     b.Property<Instant>("Expires") | ||||||
|  |                         .HasColumnType("timestamp with time zone") | ||||||
|  |                         .HasColumnName("expires"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Key") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("key"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Value") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("value"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("Id") | ||||||
|  |                         .HasName("pk_temporary_keys"); | ||||||
|  | 
 | ||||||
|  |                     b.HasIndex("Key") | ||||||
|  |                         .IsUnique() | ||||||
|  |                         .HasDatabaseName("ix_temporary_keys_key"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("temporary_keys", (string)null); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Foxnouns.Backend.Database.Models.Token", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<long>("Id") | ||||||
|  |                         .HasColumnType("bigint") | ||||||
|  |                         .HasColumnName("id"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<long>("ApplicationId") | ||||||
|  |                         .HasColumnType("bigint") | ||||||
|  |                         .HasColumnName("application_id"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<Instant>("ExpiresAt") | ||||||
|  |                         .HasColumnType("timestamp with time zone") | ||||||
|  |                         .HasColumnName("expires_at"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<byte[]>("Hash") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("bytea") | ||||||
|  |                         .HasColumnName("hash"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<bool>("ManuallyExpired") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("manually_expired"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string[]>("Scopes") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("text[]") | ||||||
|  |                         .HasColumnName("scopes"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<long>("UserId") | ||||||
|  |                         .HasColumnType("bigint") | ||||||
|  |                         .HasColumnName("user_id"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("Id") | ||||||
|  |                         .HasName("pk_tokens"); | ||||||
|  | 
 | ||||||
|  |                     b.HasIndex("ApplicationId") | ||||||
|  |                         .HasDatabaseName("ix_tokens_application_id"); | ||||||
|  | 
 | ||||||
|  |                     b.HasIndex("UserId") | ||||||
|  |                         .HasDatabaseName("ix_tokens_user_id"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("tokens", (string)null); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<long>("Id") | ||||||
|  |                         .HasColumnType("bigint") | ||||||
|  |                         .HasColumnName("id"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Avatar") | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("avatar"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Bio") | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("bio"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<Dictionary<Snowflake, User.CustomPreference>>("CustomPreferences") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("jsonb") | ||||||
|  |                         .HasColumnName("custom_preferences"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<bool>("Deleted") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("deleted"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<Instant?>("DeletedAt") | ||||||
|  |                         .HasColumnType("timestamp with time zone") | ||||||
|  |                         .HasColumnName("deleted_at"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<long?>("DeletedBy") | ||||||
|  |                         .HasColumnType("bigint") | ||||||
|  |                         .HasColumnName("deleted_by"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("DisplayName") | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("display_name"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<List<Field>>("Fields") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("jsonb") | ||||||
|  |                         .HasColumnName("fields"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<Instant>("LastActive") | ||||||
|  |                         .HasColumnType("timestamp with time zone") | ||||||
|  |                         .HasColumnName("last_active"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string[]>("Links") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("text[]") | ||||||
|  |                         .HasColumnName("links"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<bool>("ListHidden") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("list_hidden"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("MemberTitle") | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("member_title"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<List<FieldEntry>>("Names") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("jsonb") | ||||||
|  |                         .HasColumnName("names"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Password") | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("password"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<List<Pronoun>>("Pronouns") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("jsonb") | ||||||
|  |                         .HasColumnName("pronouns"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<int>("Role") | ||||||
|  |                         .HasColumnType("integer") | ||||||
|  |                         .HasColumnName("role"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<UserSettings>("Settings") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("jsonb") | ||||||
|  |                         .HasColumnName("settings"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Username") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("username"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("Id") | ||||||
|  |                         .HasName("pk_users"); | ||||||
|  | 
 | ||||||
|  |                     b.HasIndex("Username") | ||||||
|  |                         .IsUnique() | ||||||
|  |                         .HasDatabaseName("ix_users_username"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("users", (string)null); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Foxnouns.Backend.Database.Models.AuthMethod", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("Foxnouns.Backend.Database.Models.FediverseApplication", "FediverseApplication") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("FediverseApplicationId") | ||||||
|  |                         .HasConstraintName("fk_auth_methods_fediverse_applications_fediverse_application_id"); | ||||||
|  | 
 | ||||||
|  |                     b.HasOne("Foxnouns.Backend.Database.Models.User", "User") | ||||||
|  |                         .WithMany("AuthMethods") | ||||||
|  |                         .HasForeignKey("UserId") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasConstraintName("fk_auth_methods_users_user_id"); | ||||||
|  | 
 | ||||||
|  |                     b.Navigation("FediverseApplication"); | ||||||
|  | 
 | ||||||
|  |                     b.Navigation("User"); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Foxnouns.Backend.Database.Models.Member", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("Foxnouns.Backend.Database.Models.User", "User") | ||||||
|  |                         .WithMany("Members") | ||||||
|  |                         .HasForeignKey("UserId") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasConstraintName("fk_members_users_user_id"); | ||||||
|  | 
 | ||||||
|  |                     b.Navigation("User"); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Foxnouns.Backend.Database.Models.Token", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("Foxnouns.Backend.Database.Models.Application", "Application") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("ApplicationId") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasConstraintName("fk_tokens_applications_application_id"); | ||||||
|  | 
 | ||||||
|  |                     b.HasOne("Foxnouns.Backend.Database.Models.User", "User") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("UserId") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasConstraintName("fk_tokens_users_user_id"); | ||||||
|  | 
 | ||||||
|  |                     b.Navigation("Application"); | ||||||
|  | 
 | ||||||
|  |                     b.Navigation("User"); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b => | ||||||
|  |                 { | ||||||
|  |                     b.Navigation("AuthMethods"); | ||||||
|  | 
 | ||||||
|  |                     b.Navigation("Members"); | ||||||
|  |                 }); | ||||||
|  | #pragma warning restore 612, 618 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,30 @@ | ||||||
|  | using Foxnouns.Backend.Database.Models; | ||||||
|  | using Microsoft.EntityFrameworkCore.Migrations; | ||||||
|  | 
 | ||||||
|  | #nullable disable | ||||||
|  | 
 | ||||||
|  | namespace Foxnouns.Backend.Database.Migrations | ||||||
|  | { | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     public partial class AddUserSettings : Migration | ||||||
|  |     { | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         protected override void Up(MigrationBuilder migrationBuilder) | ||||||
|  |         { | ||||||
|  |             migrationBuilder.AddColumn<UserSettings>( | ||||||
|  |                 name: "settings", | ||||||
|  |                 table: "users", | ||||||
|  |                 type: "jsonb", | ||||||
|  |                 nullable: false, | ||||||
|  |                 defaultValueSql: "'{}'"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         protected override void Down(MigrationBuilder migrationBuilder) | ||||||
|  |         { | ||||||
|  |             migrationBuilder.DropColumn( | ||||||
|  |                 name: "settings", | ||||||
|  |                 table: "users"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -150,6 +150,11 @@ namespace Foxnouns.Backend.Database.Migrations | ||||||
|                         .HasColumnType("text") |                         .HasColumnType("text") | ||||||
|                         .HasColumnName("display_name"); |                         .HasColumnName("display_name"); | ||||||
| 
 | 
 | ||||||
|  |                     b.Property<List<Field>>("Fields") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("jsonb") | ||||||
|  |                         .HasColumnName("fields"); | ||||||
|  | 
 | ||||||
|                     b.Property<string[]>("Links") |                     b.Property<string[]>("Links") | ||||||
|                         .IsRequired() |                         .IsRequired() | ||||||
|                         .HasColumnType("text[]") |                         .HasColumnType("text[]") | ||||||
|  | @ -160,6 +165,16 @@ namespace Foxnouns.Backend.Database.Migrations | ||||||
|                         .HasColumnType("text") |                         .HasColumnType("text") | ||||||
|                         .HasColumnName("name"); |                         .HasColumnName("name"); | ||||||
| 
 | 
 | ||||||
|  |                     b.Property<List<FieldEntry>>("Names") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("jsonb") | ||||||
|  |                         .HasColumnName("names"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<List<Pronoun>>("Pronouns") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("jsonb") | ||||||
|  |                         .HasColumnName("pronouns"); | ||||||
|  | 
 | ||||||
|                     b.Property<bool>("Unlisted") |                     b.Property<bool>("Unlisted") | ||||||
|                         .HasColumnType("boolean") |                         .HasColumnType("boolean") | ||||||
|                         .HasColumnName("unlisted"); |                         .HasColumnName("unlisted"); | ||||||
|  | @ -269,7 +284,7 @@ namespace Foxnouns.Backend.Database.Migrations | ||||||
|                         .HasColumnType("text") |                         .HasColumnType("text") | ||||||
|                         .HasColumnName("bio"); |                         .HasColumnName("bio"); | ||||||
| 
 | 
 | ||||||
|                     b.Property<Dictionary<Guid, User.CustomPreference>>("CustomPreferences") |                     b.Property<Dictionary<Snowflake, User.CustomPreference>>("CustomPreferences") | ||||||
|                         .IsRequired() |                         .IsRequired() | ||||||
|                         .HasColumnType("jsonb") |                         .HasColumnType("jsonb") | ||||||
|                         .HasColumnName("custom_preferences"); |                         .HasColumnName("custom_preferences"); | ||||||
|  | @ -290,6 +305,11 @@ namespace Foxnouns.Backend.Database.Migrations | ||||||
|                         .HasColumnType("text") |                         .HasColumnType("text") | ||||||
|                         .HasColumnName("display_name"); |                         .HasColumnName("display_name"); | ||||||
| 
 | 
 | ||||||
|  |                     b.Property<List<Field>>("Fields") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("jsonb") | ||||||
|  |                         .HasColumnName("fields"); | ||||||
|  | 
 | ||||||
|                     b.Property<Instant>("LastActive") |                     b.Property<Instant>("LastActive") | ||||||
|                         .HasColumnType("timestamp with time zone") |                         .HasColumnType("timestamp with time zone") | ||||||
|                         .HasColumnName("last_active"); |                         .HasColumnName("last_active"); | ||||||
|  | @ -307,14 +327,29 @@ namespace Foxnouns.Backend.Database.Migrations | ||||||
|                         .HasColumnType("text") |                         .HasColumnType("text") | ||||||
|                         .HasColumnName("member_title"); |                         .HasColumnName("member_title"); | ||||||
| 
 | 
 | ||||||
|  |                     b.Property<List<FieldEntry>>("Names") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("jsonb") | ||||||
|  |                         .HasColumnName("names"); | ||||||
|  | 
 | ||||||
|                     b.Property<string>("Password") |                     b.Property<string>("Password") | ||||||
|                         .HasColumnType("text") |                         .HasColumnType("text") | ||||||
|                         .HasColumnName("password"); |                         .HasColumnName("password"); | ||||||
| 
 | 
 | ||||||
|  |                     b.Property<List<Pronoun>>("Pronouns") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("jsonb") | ||||||
|  |                         .HasColumnName("pronouns"); | ||||||
|  | 
 | ||||||
|                     b.Property<int>("Role") |                     b.Property<int>("Role") | ||||||
|                         .HasColumnType("integer") |                         .HasColumnType("integer") | ||||||
|                         .HasColumnName("role"); |                         .HasColumnName("role"); | ||||||
| 
 | 
 | ||||||
|  |                     b.Property<UserSettings>("Settings") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("jsonb") | ||||||
|  |                         .HasColumnName("settings"); | ||||||
|  | 
 | ||||||
|                     b.Property<string>("Username") |                     b.Property<string>("Username") | ||||||
|                         .IsRequired() |                         .IsRequired() | ||||||
|                         .HasColumnType("text") |                         .HasColumnType("text") | ||||||
|  | @ -358,72 +393,6 @@ namespace Foxnouns.Backend.Database.Migrations | ||||||
|                         .IsRequired() |                         .IsRequired() | ||||||
|                         .HasConstraintName("fk_members_users_user_id"); |                         .HasConstraintName("fk_members_users_user_id"); | ||||||
| 
 | 
 | ||||||
|                     b.OwnsOne("System.Collections.Generic.List<Foxnouns.Backend.Database.Models.Field>", "Fields", b1 => |  | ||||||
|                         { |  | ||||||
|                             b1.Property<long>("MemberId") |  | ||||||
|                                 .HasColumnType("bigint"); |  | ||||||
| 
 |  | ||||||
|                             b1.Property<int>("Capacity") |  | ||||||
|                                 .HasColumnType("integer"); |  | ||||||
| 
 |  | ||||||
|                             b1.HasKey("MemberId"); |  | ||||||
| 
 |  | ||||||
|                             b1.ToTable("members"); |  | ||||||
| 
 |  | ||||||
|                             b1.ToJson("fields"); |  | ||||||
| 
 |  | ||||||
|                             b1.WithOwner() |  | ||||||
|                                 .HasForeignKey("MemberId") |  | ||||||
|                                 .HasConstraintName("fk_members_members_id"); |  | ||||||
|                         }); |  | ||||||
| 
 |  | ||||||
|                     b.OwnsOne("System.Collections.Generic.List<Foxnouns.Backend.Database.Models.FieldEntry>", "Names", b1 => |  | ||||||
|                         { |  | ||||||
|                             b1.Property<long>("MemberId") |  | ||||||
|                                 .HasColumnType("bigint"); |  | ||||||
| 
 |  | ||||||
|                             b1.Property<int>("Capacity") |  | ||||||
|                                 .HasColumnType("integer"); |  | ||||||
| 
 |  | ||||||
|                             b1.HasKey("MemberId"); |  | ||||||
| 
 |  | ||||||
|                             b1.ToTable("members"); |  | ||||||
| 
 |  | ||||||
|                             b1.ToJson("names"); |  | ||||||
| 
 |  | ||||||
|                             b1.WithOwner() |  | ||||||
|                                 .HasForeignKey("MemberId") |  | ||||||
|                                 .HasConstraintName("fk_members_members_id"); |  | ||||||
|                         }); |  | ||||||
| 
 |  | ||||||
|                     b.OwnsOne("System.Collections.Generic.List<Foxnouns.Backend.Database.Models.Pronoun>", "Pronouns", b1 => |  | ||||||
|                         { |  | ||||||
|                             b1.Property<long>("MemberId") |  | ||||||
|                                 .HasColumnType("bigint"); |  | ||||||
| 
 |  | ||||||
|                             b1.Property<int>("Capacity") |  | ||||||
|                                 .HasColumnType("integer"); |  | ||||||
| 
 |  | ||||||
|                             b1.HasKey("MemberId"); |  | ||||||
| 
 |  | ||||||
|                             b1.ToTable("members"); |  | ||||||
| 
 |  | ||||||
|                             b1.ToJson("pronouns"); |  | ||||||
| 
 |  | ||||||
|                             b1.WithOwner() |  | ||||||
|                                 .HasForeignKey("MemberId") |  | ||||||
|                                 .HasConstraintName("fk_members_members_id"); |  | ||||||
|                         }); |  | ||||||
| 
 |  | ||||||
|                     b.Navigation("Fields") |  | ||||||
|                         .IsRequired(); |  | ||||||
| 
 |  | ||||||
|                     b.Navigation("Names") |  | ||||||
|                         .IsRequired(); |  | ||||||
| 
 |  | ||||||
|                     b.Navigation("Pronouns") |  | ||||||
|                         .IsRequired(); |  | ||||||
| 
 |  | ||||||
|                     b.Navigation("User"); |                     b.Navigation("User"); | ||||||
|                 }); |                 }); | ||||||
| 
 | 
 | ||||||
|  | @ -448,78 +417,6 @@ namespace Foxnouns.Backend.Database.Migrations | ||||||
|                     b.Navigation("User"); |                     b.Navigation("User"); | ||||||
|                 }); |                 }); | ||||||
| 
 | 
 | ||||||
|             modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b => |  | ||||||
|                 { |  | ||||||
|                     b.OwnsOne("Foxnouns.Backend.Database.Models.User.Fields#List", "Fields", b1 => |  | ||||||
|                         { |  | ||||||
|                             b1.Property<long>("UserId") |  | ||||||
|                                 .HasColumnType("bigint"); |  | ||||||
| 
 |  | ||||||
|                             b1.Property<int>("Capacity") |  | ||||||
|                                 .HasColumnType("integer"); |  | ||||||
| 
 |  | ||||||
|                             b1.HasKey("UserId") |  | ||||||
|                                 .HasName("pk_users"); |  | ||||||
| 
 |  | ||||||
|                             b1.ToTable("users"); |  | ||||||
| 
 |  | ||||||
|                             b1.ToJson("fields"); |  | ||||||
| 
 |  | ||||||
|                             b1.WithOwner() |  | ||||||
|                                 .HasForeignKey("UserId") |  | ||||||
|                                 .HasConstraintName("fk_users_users_user_id"); |  | ||||||
|                         }); |  | ||||||
| 
 |  | ||||||
|                     b.OwnsOne("Foxnouns.Backend.Database.Models.User.Names#List", "Names", b1 => |  | ||||||
|                         { |  | ||||||
|                             b1.Property<long>("UserId") |  | ||||||
|                                 .HasColumnType("bigint"); |  | ||||||
| 
 |  | ||||||
|                             b1.Property<int>("Capacity") |  | ||||||
|                                 .HasColumnType("integer"); |  | ||||||
| 
 |  | ||||||
|                             b1.HasKey("UserId") |  | ||||||
|                                 .HasName("pk_users"); |  | ||||||
| 
 |  | ||||||
|                             b1.ToTable("users"); |  | ||||||
| 
 |  | ||||||
|                             b1.ToJson("names"); |  | ||||||
| 
 |  | ||||||
|                             b1.WithOwner() |  | ||||||
|                                 .HasForeignKey("UserId") |  | ||||||
|                                 .HasConstraintName("fk_users_users_user_id"); |  | ||||||
|                         }); |  | ||||||
| 
 |  | ||||||
|                     b.OwnsOne("Foxnouns.Backend.Database.Models.User.Pronouns#List", "Pronouns", b1 => |  | ||||||
|                         { |  | ||||||
|                             b1.Property<long>("UserId") |  | ||||||
|                                 .HasColumnType("bigint"); |  | ||||||
| 
 |  | ||||||
|                             b1.Property<int>("Capacity") |  | ||||||
|                                 .HasColumnType("integer"); |  | ||||||
| 
 |  | ||||||
|                             b1.HasKey("UserId") |  | ||||||
|                                 .HasName("pk_users"); |  | ||||||
| 
 |  | ||||||
|                             b1.ToTable("users"); |  | ||||||
| 
 |  | ||||||
|                             b1.ToJson("pronouns"); |  | ||||||
| 
 |  | ||||||
|                             b1.WithOwner() |  | ||||||
|                                 .HasForeignKey("UserId") |  | ||||||
|                                 .HasConstraintName("fk_users_users_user_id"); |  | ||||||
|                         }); |  | ||||||
| 
 |  | ||||||
|                     b.Navigation("Fields") |  | ||||||
|                         .IsRequired(); |  | ||||||
| 
 |  | ||||||
|                     b.Navigation("Names") |  | ||||||
|                         .IsRequired(); |  | ||||||
| 
 |  | ||||||
|                     b.Navigation("Pronouns") |  | ||||||
|                         .IsRequired(); |  | ||||||
|                 }); |  | ||||||
| 
 |  | ||||||
|             modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b => |             modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b => | ||||||
|                 { |                 { | ||||||
|                     b.Navigation("AuthMethods"); |                     b.Navigation("AuthMethods"); | ||||||
|  |  | ||||||
|  | @ -25,6 +25,7 @@ public class User : BaseModel | ||||||
| 
 | 
 | ||||||
|     public List<Member> Members { get; } = []; |     public List<Member> Members { get; } = []; | ||||||
|     public List<AuthMethod> AuthMethods { get; } = []; |     public List<AuthMethod> AuthMethods { get; } = []; | ||||||
|  |     public UserSettings Settings { get; set; } = new(); | ||||||
| 
 | 
 | ||||||
|     public required Instant LastActive { get; set; } |     public required Instant LastActive { get; set; } | ||||||
| 
 | 
 | ||||||
|  | @ -59,3 +60,8 @@ public enum PreferenceSize | ||||||
|     Normal, |     Normal, | ||||||
|     Small, |     Small, | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | public class UserSettings | ||||||
|  | { | ||||||
|  |     public bool? DarkMode { get; set; } = null; | ||||||
|  | } | ||||||
|  | @ -23,11 +23,15 @@ public class ApiError(string message, HttpStatusCode? statusCode = null, ErrorCo | ||||||
|     public readonly HttpStatusCode StatusCode = statusCode ?? HttpStatusCode.InternalServerError; |     public readonly HttpStatusCode StatusCode = statusCode ?? HttpStatusCode.InternalServerError; | ||||||
|     public readonly ErrorCode ErrorCode = errorCode ?? ErrorCode.InternalServerError; |     public readonly ErrorCode ErrorCode = errorCode ?? ErrorCode.InternalServerError; | ||||||
| 
 | 
 | ||||||
|     public class Unauthorized(string message) : ApiError(message, statusCode: HttpStatusCode.Unauthorized, |     public class Unauthorized(string message, ErrorCode errorCode = ErrorCode.AuthenticationError) : ApiError(message, | ||||||
|         errorCode: ErrorCode.AuthenticationError); |         statusCode: HttpStatusCode.Unauthorized, | ||||||
|  |         errorCode: errorCode); | ||||||
| 
 | 
 | ||||||
|     public class Forbidden(string message, IEnumerable<string>? scopes = null) |     public class Forbidden( | ||||||
|         : ApiError(message, statusCode: HttpStatusCode.Forbidden) |         string message, | ||||||
|  |         IEnumerable<string>? scopes = null, | ||||||
|  |         ErrorCode errorCode = ErrorCode.Forbidden) | ||||||
|  |         : ApiError(message, statusCode: HttpStatusCode.Forbidden, errorCode: errorCode) | ||||||
|     { |     { | ||||||
|         public readonly string[] Scopes = scopes?.ToArray() ?? []; |         public readonly string[] Scopes = scopes?.ToArray() ?? []; | ||||||
|     } |     } | ||||||
|  | @ -115,6 +119,8 @@ public enum ErrorCode | ||||||
|     Forbidden, |     Forbidden, | ||||||
|     BadRequest, |     BadRequest, | ||||||
|     AuthenticationError, |     AuthenticationError, | ||||||
|  |     AuthenticationRequired, | ||||||
|  |     MissingScopes, | ||||||
|     GenericApiError, |     GenericApiError, | ||||||
|     UserNotFound, |     UserNotFound, | ||||||
|     MemberNotFound, |     MemberNotFound, | ||||||
|  |  | ||||||
|  | @ -18,10 +18,10 @@ public class AuthorizationMiddleware : IMiddleware | ||||||
| 
 | 
 | ||||||
|         var token = ctx.GetToken(); |         var token = ctx.GetToken(); | ||||||
|         if (token == null) |         if (token == null) | ||||||
|             throw new ApiError.Unauthorized("This endpoint requires an authenticated user."); |             throw new ApiError.Unauthorized("This endpoint requires an authenticated user.", ErrorCode.AuthenticationRequired); | ||||||
|         if (attribute.Scopes.Length > 0 && attribute.Scopes.Except(token.Scopes.ExpandScopes()).Any()) |         if (attribute.Scopes.Length > 0 && attribute.Scopes.Except(token.Scopes.ExpandScopes()).Any()) | ||||||
|             throw new ApiError.Forbidden("This endpoint requires ungranted scopes.", |             throw new ApiError.Forbidden("This endpoint requires ungranted scopes.", | ||||||
|                 attribute.Scopes.Except(token.Scopes.ExpandScopes())); |                 attribute.Scopes.Except(token.Scopes.ExpandScopes()), ErrorCode.MissingScopes); | ||||||
|         if (attribute.RequireAdmin && token.User.Role != UserRole.Admin) |         if (attribute.RequireAdmin && token.User.Role != UserRole.Admin) | ||||||
|             throw new ApiError.Forbidden("This endpoint can only be used by admins."); |             throw new ApiError.Forbidden("This endpoint can only be used by admins."); | ||||||
|         if (attribute.RequireModerator && token.User.Role != UserRole.Admin && token.User.Role != UserRole.Moderator) |         if (attribute.RequireModerator && token.User.Role != UserRole.Admin && token.User.Role != UserRole.Moderator) | ||||||
|  |  | ||||||
|  | @ -12,7 +12,8 @@ public class UserRendererService(DatabaseContext db, MemberRendererService membe | ||||||
|     public async Task<UserResponse> RenderUserAsync(User user, User? selfUser = null, |     public async Task<UserResponse> RenderUserAsync(User user, User? selfUser = null, | ||||||
|         Token? token = null, |         Token? token = null, | ||||||
|         bool renderMembers = true, |         bool renderMembers = true, | ||||||
|         bool renderAuthMethods = false) |         bool renderAuthMethods = false, | ||||||
|  |         CancellationToken ct = default) | ||||||
|     { |     { | ||||||
|         var isSelfUser = selfUser?.Id == user.Id; |         var isSelfUser = selfUser?.Id == user.Id; | ||||||
|         var tokenCanReadHiddenMembers = token.HasScope("member.read") && isSelfUser; |         var tokenCanReadHiddenMembers = token.HasScope("member.read") && isSelfUser; | ||||||
|  | @ -24,7 +25,7 @@ public class UserRendererService(DatabaseContext db, MemberRendererService membe | ||||||
|         renderAuthMethods = renderAuthMethods && tokenPrivileged; |         renderAuthMethods = renderAuthMethods && tokenPrivileged; | ||||||
| 
 | 
 | ||||||
|         IEnumerable<Member> members = |         IEnumerable<Member> members = | ||||||
|             renderMembers ? await db.Members.Where(m => m.UserId == user.Id).ToListAsync() : []; |             renderMembers ? await db.Members.Where(m => m.UserId == user.Id).ToListAsync(ct) : []; | ||||||
|         // Unless the user is requesting their own members AND the token can read hidden members, we filter out unlisted members. |         // Unless the user is requesting their own members AND the token can read hidden members, we filter out unlisted members. | ||||||
|         if (!(isSelfUser && tokenCanReadHiddenMembers)) members = members.Where(m => m.Unlisted); |         if (!(isSelfUser && tokenCanReadHiddenMembers)) members = members.Where(m => m.Unlisted); | ||||||
| 
 | 
 | ||||||
|  | @ -32,7 +33,7 @@ public class UserRendererService(DatabaseContext db, MemberRendererService membe | ||||||
|             ? await db.AuthMethods |             ? await db.AuthMethods | ||||||
|                 .Where(a => a.UserId == user.Id) |                 .Where(a => a.UserId == user.Id) | ||||||
|                 .Include(a => a.FediverseApplication) |                 .Include(a => a.FediverseApplication) | ||||||
|                 .ToListAsync() |                 .ToListAsync(ct) | ||||||
|             : []; |             : []; | ||||||
| 
 | 
 | ||||||
|         return new UserResponse( |         return new UserResponse( | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue