feat(backend): initial /api/v1/users endpoint
This commit is contained in:
		
							parent
							
								
									5e7df2e074
								
							
						
					
					
						commit
						7791c91960
					
				
					 20 changed files with 385 additions and 4 deletions
				
			
		|  | @ -22,6 +22,7 @@ using Foxnouns.Backend.Services; | |||
| using Foxnouns.Backend.Utils; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using XidNet; | ||||
| 
 | ||||
| namespace Foxnouns.Backend.Controllers; | ||||
| 
 | ||||
|  | @ -64,6 +65,7 @@ public class FlagsController( | |||
|         var flag = new PrideFlag | ||||
|         { | ||||
|             Id = snowflakeGenerator.GenerateSnowflake(), | ||||
|             LegacyId = Xid.NewXid().ToString(), | ||||
|             UserId = CurrentUser!.Id, | ||||
|             Name = req.Name, | ||||
|             Description = req.Description, | ||||
|  |  | |||
|  | @ -26,6 +26,7 @@ using Microsoft.AspNetCore.Mvc; | |||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.EntityFrameworkCore.Storage; | ||||
| using NodaTime; | ||||
| using XidNet; | ||||
| 
 | ||||
| namespace Foxnouns.Backend.Controllers; | ||||
| 
 | ||||
|  | @ -101,6 +102,7 @@ public class MembersController( | |||
|         var member = new Member | ||||
|         { | ||||
|             Id = snowflakeGenerator.GenerateSnowflake(), | ||||
|             LegacyId = Xid.NewXid().ToString(), | ||||
|             User = CurrentUser!, | ||||
|             Name = req.Name, | ||||
|             DisplayName = req.DisplayName, | ||||
|  |  | |||
|  | @ -222,7 +222,7 @@ public class UsersController( | |||
|             .CustomPreferences.Where(x => req.Any(r => r.Id == x.Key)) | ||||
|             .ToDictionary(); | ||||
| 
 | ||||
|         foreach (CustomPreferenceUpdateRequest? r in req) | ||||
|         foreach (CustomPreferenceUpdateRequest r in req) | ||||
|         { | ||||
|             if (r.Id != null && preferences.ContainsKey(r.Id.Value)) | ||||
|             { | ||||
|  | @ -233,6 +233,7 @@ public class UsersController( | |||
|                     Muted = r.Muted, | ||||
|                     Size = r.Size, | ||||
|                     Tooltip = r.Tooltip, | ||||
|                     LegacyId = preferences[r.Id.Value].LegacyId, | ||||
|                 }; | ||||
|             } | ||||
|             else | ||||
|  | @ -244,6 +245,7 @@ public class UsersController( | |||
|                     Muted = r.Muted, | ||||
|                     Size = r.Size, | ||||
|                     Tooltip = r.Tooltip, | ||||
|                     LegacyId = Guid.NewGuid(), | ||||
|                 }; | ||||
|             } | ||||
|         } | ||||
|  |  | |||
							
								
								
									
										16
									
								
								Foxnouns.Backend/Controllers/V1/UsersV1Controller.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								Foxnouns.Backend/Controllers/V1/UsersV1Controller.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | |||
| using Foxnouns.Backend.Database.Models; | ||||
| using Foxnouns.Backend.Services.V1; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| 
 | ||||
| namespace Foxnouns.Backend.Controllers.V1; | ||||
| 
 | ||||
| [Route("/api/v1/users")] | ||||
| public class UsersV1Controller(UsersV1Service usersV1Service) : ApiControllerBase | ||||
| { | ||||
|     [HttpGet("{userRef}")] | ||||
|     public async Task<IActionResult> GetUserAsync(string userRef, CancellationToken ct = default) | ||||
|     { | ||||
|         User user = await usersV1Service.ResolveUserAsync(userRef, CurrentToken, ct); | ||||
|         return Ok(await usersV1Service.RenderUserAsync(user)); | ||||
|     } | ||||
| } | ||||
|  | @ -139,6 +139,26 @@ public class DatabaseContext(DbContextOptions options) : DbContext(options) | |||
|         modelBuilder | ||||
|             .HasDbFunction(typeof(DatabaseContext).GetMethod(nameof(FindFreeMemberSid))!) | ||||
|             .HasName("find_free_member_sid"); | ||||
| 
 | ||||
|         // Indexes for legacy IDs for APIv1 | ||||
|         modelBuilder.Entity<User>().HasIndex(u => u.LegacyId).IsUnique(); | ||||
|         modelBuilder.Entity<Member>().HasIndex(m => m.LegacyId).IsUnique(); | ||||
|         modelBuilder.Entity<PrideFlag>().HasIndex(f => f.LegacyId).IsUnique(); | ||||
| 
 | ||||
|         // a UUID is not an xid, but this should always be set by the application anyway. | ||||
|         // we're just setting it here to shut EFCore up because squashing migrations is for nerds | ||||
|         modelBuilder | ||||
|             .Entity<User>() | ||||
|             .Property(u => u.LegacyId) | ||||
|             .HasDefaultValueSql("gen_random_uuid()"); | ||||
|         modelBuilder | ||||
|             .Entity<Member>() | ||||
|             .Property(m => m.LegacyId) | ||||
|             .HasDefaultValueSql("gen_random_uuid()"); | ||||
|         modelBuilder | ||||
|             .Entity<PrideFlag>() | ||||
|             .Property(f => f.LegacyId) | ||||
|             .HasDefaultValueSql("gen_random_uuid()"); | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|  |  | |||
|  | @ -0,0 +1,78 @@ | |||
| using Microsoft.EntityFrameworkCore.Infrastructure; | ||||
| using Microsoft.EntityFrameworkCore.Migrations; | ||||
| 
 | ||||
| #nullable disable | ||||
| 
 | ||||
| namespace Foxnouns.Backend.Database.Migrations | ||||
| { | ||||
|     /// <inheritdoc /> | ||||
|     [DbContext(typeof(DatabaseContext))] | ||||
|     [Migration("20241225155818_AddLegacyIds")] | ||||
|     public partial class AddLegacyIds : Migration | ||||
|     { | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Up(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.AddColumn<string>( | ||||
|                 name: "legacy_id", | ||||
|                 table: "users", | ||||
|                 type: "text", | ||||
|                 nullable: false, | ||||
|                 defaultValueSql: "gen_random_uuid()" | ||||
|             ); | ||||
| 
 | ||||
|             migrationBuilder.AddColumn<string>( | ||||
|                 name: "legacy_id", | ||||
|                 table: "pride_flags", | ||||
|                 type: "text", | ||||
|                 nullable: false, | ||||
|                 defaultValueSql: "gen_random_uuid()" | ||||
|             ); | ||||
| 
 | ||||
|             migrationBuilder.AddColumn<string>( | ||||
|                 name: "legacy_id", | ||||
|                 table: "members", | ||||
|                 type: "text", | ||||
|                 nullable: false, | ||||
|                 defaultValueSql: "gen_random_uuid()" | ||||
|             ); | ||||
| 
 | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_users_legacy_id", | ||||
|                 table: "users", | ||||
|                 column: "legacy_id", | ||||
|                 unique: true | ||||
|             ); | ||||
| 
 | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_pride_flags_legacy_id", | ||||
|                 table: "pride_flags", | ||||
|                 column: "legacy_id", | ||||
|                 unique: true | ||||
|             ); | ||||
| 
 | ||||
|             migrationBuilder.CreateIndex( | ||||
|                 name: "ix_members_legacy_id", | ||||
|                 table: "members", | ||||
|                 column: "legacy_id", | ||||
|                 unique: true | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         /// <inheritdoc /> | ||||
|         protected override void Down(MigrationBuilder migrationBuilder) | ||||
|         { | ||||
|             migrationBuilder.DropIndex(name: "ix_users_legacy_id", table: "users"); | ||||
| 
 | ||||
|             migrationBuilder.DropIndex(name: "ix_pride_flags_legacy_id", table: "pride_flags"); | ||||
| 
 | ||||
|             migrationBuilder.DropIndex(name: "ix_members_legacy_id", table: "members"); | ||||
| 
 | ||||
|             migrationBuilder.DropColumn(name: "legacy_id", table: "users"); | ||||
| 
 | ||||
|             migrationBuilder.DropColumn(name: "legacy_id", table: "pride_flags"); | ||||
| 
 | ||||
|             migrationBuilder.DropColumn(name: "legacy_id", table: "members"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -254,6 +254,13 @@ namespace Foxnouns.Backend.Database.Migrations | |||
|                         .HasColumnType("jsonb") | ||||
|                         .HasColumnName("fields"); | ||||
| 
 | ||||
|                     b.Property<string>("LegacyId") | ||||
|                         .IsRequired() | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("legacy_id") | ||||
|                         .HasDefaultValueSql("gen_random_uuid()"); | ||||
| 
 | ||||
|                     b.PrimitiveCollection<string[]>("Links") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text[]") | ||||
|  | @ -292,6 +299,10 @@ namespace Foxnouns.Backend.Database.Migrations | |||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_members"); | ||||
| 
 | ||||
|                     b.HasIndex("LegacyId") | ||||
|                         .IsUnique() | ||||
|                         .HasDatabaseName("ix_members_legacy_id"); | ||||
| 
 | ||||
|                     b.HasIndex("Sid") | ||||
|                         .IsUnique() | ||||
|                         .HasDatabaseName("ix_members_sid"); | ||||
|  | @ -386,6 +397,13 @@ namespace Foxnouns.Backend.Database.Migrations | |||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("hash"); | ||||
| 
 | ||||
|                     b.Property<string>("LegacyId") | ||||
|                         .IsRequired() | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("legacy_id") | ||||
|                         .HasDefaultValueSql("gen_random_uuid()"); | ||||
| 
 | ||||
|                     b.Property<string>("Name") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text") | ||||
|  | @ -398,6 +416,10 @@ namespace Foxnouns.Backend.Database.Migrations | |||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_pride_flags"); | ||||
| 
 | ||||
|                     b.HasIndex("LegacyId") | ||||
|                         .IsUnique() | ||||
|                         .HasDatabaseName("ix_pride_flags_legacy_id"); | ||||
| 
 | ||||
|                     b.HasIndex("UserId") | ||||
|                         .HasDatabaseName("ix_pride_flags_user_id"); | ||||
| 
 | ||||
|  | @ -582,6 +604,13 @@ namespace Foxnouns.Backend.Database.Migrations | |||
|                         .HasColumnType("timestamp with time zone") | ||||
|                         .HasColumnName("last_sid_reroll"); | ||||
| 
 | ||||
|                     b.Property<string>("LegacyId") | ||||
|                         .IsRequired() | ||||
|                         .ValueGeneratedOnAdd() | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("legacy_id") | ||||
|                         .HasDefaultValueSql("gen_random_uuid()"); | ||||
| 
 | ||||
|                     b.PrimitiveCollection<string[]>("Links") | ||||
|                         .IsRequired() | ||||
|                         .HasColumnType("text[]") | ||||
|  | @ -637,6 +666,10 @@ namespace Foxnouns.Backend.Database.Migrations | |||
|                     b.HasKey("Id") | ||||
|                         .HasName("pk_users"); | ||||
| 
 | ||||
|                     b.HasIndex("LegacyId") | ||||
|                         .IsUnique() | ||||
|                         .HasDatabaseName("ix_users_legacy_id"); | ||||
| 
 | ||||
|                     b.HasIndex("Sid") | ||||
|                         .IsUnique() | ||||
|                         .HasDatabaseName("ix_users_sid"); | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ public class Member : BaseModel | |||
| { | ||||
|     public required string Name { get; set; } | ||||
|     public string Sid { get; set; } = string.Empty; | ||||
|     public required string LegacyId { get; init; } | ||||
|     public string? DisplayName { get; set; } | ||||
|     public string? Bio { get; set; } | ||||
|     public string? Avatar { get; set; } | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ namespace Foxnouns.Backend.Database.Models; | |||
| public class PrideFlag : BaseModel | ||||
| { | ||||
|     public required Snowflake UserId { get; init; } | ||||
|     public required string LegacyId { get; init; } | ||||
| 
 | ||||
|     // A null hash means the flag hasn't been processed yet. | ||||
|     public string? Hash { get; set; } | ||||
|  |  | |||
|  | @ -25,6 +25,7 @@ public class User : BaseModel | |||
| { | ||||
|     public required string Username { get; set; } | ||||
|     public string Sid { get; set; } = string.Empty; | ||||
|     public required string LegacyId { get; init; } | ||||
|     public string? DisplayName { get; set; } | ||||
|     public string? Bio { get; set; } | ||||
|     public string? MemberTitle { get; set; } | ||||
|  | @ -69,6 +70,8 @@ public class User : BaseModel | |||
|         // This type is generally serialized directly, so the converter is applied here. | ||||
|         [JsonConverter(typeof(ScreamingSnakeCaseEnumConverter))] | ||||
|         public PreferenceSize Size { get; set; } | ||||
| 
 | ||||
|         public Guid LegacyId { get; init; } = Guid.NewGuid(); | ||||
|     } | ||||
| 
 | ||||
|     public static readonly Duration DeleteAfter = Duration.FromDays(30); | ||||
|  |  | |||
|  | @ -36,7 +36,7 @@ public record UserResponse( | |||
|     IEnumerable<FieldEntry> Names, | ||||
|     IEnumerable<Pronoun> Pronouns, | ||||
|     IEnumerable<Field> Fields, | ||||
|     Dictionary<Snowflake, User.CustomPreference> CustomPreferences, | ||||
|     Dictionary<Snowflake, CustomPreferenceResponse> CustomPreferences, | ||||
|     IEnumerable<PrideFlagResponse> Flags, | ||||
|     int? UtcOffset, | ||||
|     [property: JsonConverter(typeof(ScreamingSnakeCaseEnumConverter))] UserRole Role, | ||||
|  | @ -52,6 +52,14 @@ public record UserResponse( | |||
|     [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] bool? Deleted | ||||
| ); | ||||
| 
 | ||||
| public record CustomPreferenceResponse( | ||||
|     string Icon, | ||||
|     string Tooltip, | ||||
|     bool Muted, | ||||
|     bool Favourite, | ||||
|     [property: JsonConverter(typeof(ScreamingSnakeCaseEnumConverter))] PreferenceSize Size | ||||
| ); | ||||
| 
 | ||||
| public record AuthMethodResponse( | ||||
|     Snowflake Id, | ||||
|     [property: JsonConverter(typeof(ScreamingSnakeCaseEnumConverter))] AuthType Type, | ||||
|  |  | |||
							
								
								
									
										77
									
								
								Foxnouns.Backend/Dto/V1/User.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								Foxnouns.Backend/Dto/V1/User.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,77 @@ | |||
| // ReSharper disable NotAccessedPositionalProperty.Global | ||||
| using Foxnouns.Backend.Database; | ||||
| using Foxnouns.Backend.Database.Models; | ||||
| using Foxnouns.Backend.Services.V1; | ||||
| using Newtonsoft.Json; | ||||
| using Newtonsoft.Json.Converters; | ||||
| using Newtonsoft.Json.Serialization; | ||||
| 
 | ||||
| namespace Foxnouns.Backend.Dto.V1; | ||||
| 
 | ||||
| public record UserResponse( | ||||
|     string Id, | ||||
|     Snowflake IdNew, | ||||
|     string Sid, | ||||
|     string Name, | ||||
|     string? DisplayName, | ||||
|     string? Bio, | ||||
|     string? MemberTitle, | ||||
|     string? Avatar, | ||||
|     string[] Links, | ||||
|     FieldEntry[] Names, | ||||
|     PronounEntry[] Pronouns, | ||||
|     ProfileField[] Fields, | ||||
|     int? UtcOffset, | ||||
|     Dictionary<Guid, CustomPreference> CustomPreferences | ||||
| ); | ||||
| 
 | ||||
| public record CustomPreference( | ||||
|     string Icon, | ||||
|     string Tooltip, | ||||
|     [property: JsonConverter(typeof(StringEnumConverter), typeof(SnakeCaseNamingStrategy))] | ||||
|         PreferenceSize Size, | ||||
|     bool Muted, | ||||
|     bool Favourite | ||||
| ); | ||||
| 
 | ||||
| public record ProfileField(string Name, FieldEntry[] Entries) | ||||
| { | ||||
|     public static ProfileField FromField( | ||||
|         Field field, | ||||
|         Dictionary<Snowflake, User.CustomPreference> customPreferences | ||||
|     ) => new(field.Name, FieldEntry.FromEntries(field.Entries, customPreferences)); | ||||
| 
 | ||||
|     public static ProfileField[] FromFields( | ||||
|         IEnumerable<Field> fields, | ||||
|         Dictionary<Snowflake, User.CustomPreference> customPreferences | ||||
|     ) => fields.Select(f => FromField(f, customPreferences)).ToArray(); | ||||
| } | ||||
| 
 | ||||
| public record FieldEntry(string Value, string Status) | ||||
| { | ||||
|     public static FieldEntry[] FromEntries( | ||||
|         IEnumerable<Foxnouns.Backend.Database.Models.FieldEntry> entries, | ||||
|         Dictionary<Snowflake, User.CustomPreference> customPreferences | ||||
|     ) => | ||||
|         entries | ||||
|             .Select(e => new FieldEntry( | ||||
|                 e.Value, | ||||
|                 V1Utils.TranslateStatus(e.Status, customPreferences) | ||||
|             )) | ||||
|             .ToArray(); | ||||
| } | ||||
| 
 | ||||
| public record PronounEntry(string Pronouns, string? DisplayText, string Status) | ||||
| { | ||||
|     public static PronounEntry[] FromPronouns( | ||||
|         IEnumerable<Pronoun> pronouns, | ||||
|         Dictionary<Snowflake, User.CustomPreference> customPreferences | ||||
|     ) => | ||||
|         pronouns | ||||
|             .Select(p => new PronounEntry( | ||||
|                 p.Value, | ||||
|                 p.DisplayText, | ||||
|                 V1Utils.TranslateStatus(p.Status, customPreferences) | ||||
|             )) | ||||
|             .ToArray(); | ||||
| } | ||||
|  | @ -19,6 +19,7 @@ using Foxnouns.Backend.Jobs; | |||
| using Foxnouns.Backend.Middleware; | ||||
| using Foxnouns.Backend.Services; | ||||
| using Foxnouns.Backend.Services.Auth; | ||||
| using Foxnouns.Backend.Services.V1; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Minio; | ||||
| using NodaTime; | ||||
|  | @ -127,7 +128,9 @@ public static class WebApplicationExtensions | |||
|                     .AddTransient<MemberAvatarUpdateInvocable>() | ||||
|                     .AddTransient<UserAvatarUpdateInvocable>() | ||||
|                     .AddTransient<CreateFlagInvocable>() | ||||
|                     .AddTransient<CreateDataExportInvocable>(); | ||||
|                     .AddTransient<CreateDataExportInvocable>() | ||||
|                     // Legacy services | ||||
|                     .AddScoped<UsersV1Service>(); | ||||
| 
 | ||||
|                 if (!config.Logging.EnableMetrics) | ||||
|                     services.AddHostedService<BackgroundMetricsCollectionService>(); | ||||
|  |  | |||
|  | @ -44,6 +44,7 @@ | |||
|         <PackageReference Include="SixLabors.ImageSharp" Version="3.1.6"/> | ||||
|         <PackageReference Include="System.Text.Json" Version="9.0.0"/> | ||||
|         <PackageReference Include="System.Text.RegularExpressions" Version="4.3.1"/> | ||||
|         <PackageReference Include="Yort.Xid.Net" Version="2.0.1"/> | ||||
|     </ItemGroup> | ||||
| 
 | ||||
|     <Target Name="SetSourceRevisionId" BeforeTargets="InitializeSourceControlInformation"> | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ using Foxnouns.Backend.Utils; | |||
| using Microsoft.AspNetCore.Identity; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using NodaTime; | ||||
| using XidNet; | ||||
| 
 | ||||
| namespace Foxnouns.Backend.Services.Auth; | ||||
| 
 | ||||
|  | @ -70,6 +71,7 @@ public class AuthService( | |||
|             }, | ||||
|             LastActive = clock.GetCurrentInstant(), | ||||
|             Sid = null!, | ||||
|             LegacyId = Xid.NewXid().ToString(), | ||||
|         }; | ||||
| 
 | ||||
|         db.Add(user); | ||||
|  | @ -116,6 +118,7 @@ public class AuthService( | |||
|             }, | ||||
|             LastActive = clock.GetCurrentInstant(), | ||||
|             Sid = null!, | ||||
|             LegacyId = Xid.NewXid().ToString(), | ||||
|         }; | ||||
| 
 | ||||
|         db.Add(user); | ||||
|  |  | |||
|  | @ -103,7 +103,8 @@ public class UserRendererService( | |||
|             user.Names, | ||||
|             user.Pronouns, | ||||
|             user.Fields, | ||||
|             user.CustomPreferences, | ||||
|             user.CustomPreferences.Select(x => (x.Key, RenderCustomPreference(x.Value))) | ||||
|                 .ToDictionary(), | ||||
|             flags.Select(f => RenderPrideFlag(f.PrideFlag)), | ||||
|             utcOffset, | ||||
|             user.Role, | ||||
|  | @ -130,6 +131,14 @@ public class UserRendererService( | |||
|                 : a.RemoteUsername | ||||
|         ); | ||||
| 
 | ||||
|     public static CustomPreferenceResponse RenderCustomPreference(User.CustomPreference pref) => | ||||
|         new(pref.Icon, pref.Tooltip, pref.Muted, pref.Favourite, pref.Size); | ||||
| 
 | ||||
|     public static Dictionary<Snowflake, CustomPreferenceResponse> RenderCustomPreferences( | ||||
|         User user | ||||
|     ) => | ||||
|         user.CustomPreferences.Select(x => (x.Key, RenderCustomPreference(x.Value))).ToDictionary(); | ||||
| 
 | ||||
|     public PartialUser RenderPartialUser(User user) => | ||||
|         new( | ||||
|             user.Id, | ||||
|  |  | |||
							
								
								
									
										92
									
								
								Foxnouns.Backend/Services/V1/UsersV1Service.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								Foxnouns.Backend/Services/V1/UsersV1Service.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,92 @@ | |||
| using Foxnouns.Backend.Database; | ||||
| using Foxnouns.Backend.Database.Models; | ||||
| using Foxnouns.Backend.Dto.V1; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using FieldEntry = Foxnouns.Backend.Dto.V1.FieldEntry; | ||||
| 
 | ||||
| namespace Foxnouns.Backend.Services.V1; | ||||
| 
 | ||||
| public class UsersV1Service(DatabaseContext db) | ||||
| { | ||||
|     public async Task<User> ResolveUserAsync( | ||||
|         string userRef, | ||||
|         Token? token, | ||||
|         CancellationToken ct = default | ||||
|     ) | ||||
|     { | ||||
|         if (userRef == "@me") | ||||
|         { | ||||
|             if (token == null) | ||||
|             { | ||||
|                 throw new ApiError.Unauthorized( | ||||
|                     "This endpoint requires an authenticated user.", | ||||
|                     ErrorCode.AuthenticationRequired | ||||
|                 ); | ||||
|             } | ||||
| 
 | ||||
|             return await db.Users.FirstAsync(u => u.Id == token.UserId, ct); | ||||
|         } | ||||
| 
 | ||||
|         User? user; | ||||
|         if (Snowflake.TryParse(userRef, out Snowflake? sf)) | ||||
|         { | ||||
|             user = await db.Users.FirstOrDefaultAsync(u => u.Id == sf && !u.Deleted, ct); | ||||
|             if (user != null) | ||||
|                 return user; | ||||
|         } | ||||
| 
 | ||||
|         user = await db.Users.FirstOrDefaultAsync(u => u.LegacyId == userRef && !u.Deleted, ct); | ||||
|         if (user != null) | ||||
|             return user; | ||||
| 
 | ||||
|         user = await db.Users.FirstOrDefaultAsync(u => u.Username == userRef && !u.Deleted, ct); | ||||
|         if (user != null) | ||||
|             return user; | ||||
| 
 | ||||
|         throw new ApiError.NotFound( | ||||
|             "No user with that ID or username found.", | ||||
|             ErrorCode.UserNotFound | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     public async Task<UserResponse> RenderUserAsync(User user) | ||||
|     { | ||||
|         int? utcOffset = null; | ||||
|         if ( | ||||
|             user.Timezone != null | ||||
|             && TimeZoneInfo.TryFindSystemTimeZoneById(user.Timezone, out TimeZoneInfo? tz) | ||||
|         ) | ||||
|         { | ||||
|             utcOffset = (int)tz.GetUtcOffset(DateTimeOffset.UtcNow).TotalSeconds; | ||||
|         } | ||||
| 
 | ||||
|         return new UserResponse( | ||||
|             user.LegacyId, | ||||
|             user.Id, | ||||
|             user.Sid, | ||||
|             user.Username, | ||||
|             user.DisplayName, | ||||
|             user.Bio, | ||||
|             user.MemberTitle, | ||||
|             user.Avatar, | ||||
|             user.Links, | ||||
|             FieldEntry.FromEntries(user.Names, user.CustomPreferences), | ||||
|             PronounEntry.FromPronouns(user.Pronouns, user.CustomPreferences), | ||||
|             ProfileField.FromFields(user.Fields, user.CustomPreferences), | ||||
|             utcOffset, | ||||
|             user.CustomPreferences.Select(x => | ||||
|                     ( | ||||
|                         x.Value.LegacyId, | ||||
|                         new CustomPreference( | ||||
|                             x.Value.Icon, | ||||
|                             x.Value.Tooltip, | ||||
|                             x.Value.Size, | ||||
|                             x.Value.Muted, | ||||
|                             x.Value.Favourite | ||||
|                         ) | ||||
|                     ) | ||||
|                 ) | ||||
|                 .ToDictionary() | ||||
|         ); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										20
									
								
								Foxnouns.Backend/Services/V1/V1Utils.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								Foxnouns.Backend/Services/V1/V1Utils.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | |||
| using Foxnouns.Backend.Database; | ||||
| using Foxnouns.Backend.Database.Models; | ||||
| 
 | ||||
| namespace Foxnouns.Backend.Services.V1; | ||||
| 
 | ||||
| public static class V1Utils | ||||
| { | ||||
|     public static string TranslateStatus( | ||||
|         string status, | ||||
|         Dictionary<Snowflake, User.CustomPreference> customPreferences | ||||
|     ) | ||||
|     { | ||||
|         if (!Snowflake.TryParse(status, out Snowflake? sf)) | ||||
|             return status; | ||||
| 
 | ||||
|         return customPreferences.TryGetValue(sf.Value, out User.CustomPreference? cf) | ||||
|             ? cf.LegacyId.ToString() | ||||
|             : "unknown"; | ||||
|     } | ||||
| } | ||||
|  | @ -293,6 +293,12 @@ | |||
|           "System.Runtime": "4.3.1" | ||||
|         } | ||||
|       }, | ||||
|       "Yort.Xid.Net": { | ||||
|         "type": "Direct", | ||||
|         "requested": "[2.0.1, )", | ||||
|         "resolved": "2.0.1", | ||||
|         "contentHash": "+3sNX7/RKSKheVuMz9jtWLazD+R4PXpx8va2d9SdDgvKOhETbEb0VYis8K/fD1qm/qOQT57LadToSpzReGMZlw==" | ||||
|       }, | ||||
|       "BouncyCastle.Cryptography": { | ||||
|         "type": "Transitive", | ||||
|         "resolved": "2.5.0", | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue