Compare commits
	
		
			No commits in common. "478ba2a4065f1aa18e2dc5c5f8f9db761ece6e16" and "7791c9196091d9c6bbeb2fda46f0913b925cc4b7" have entirely different histories.
		
	
	
		
			478ba2a406
			...
			7791c91960
		
	
		
					 15 changed files with 38 additions and 458 deletions
				
			
		| 
						 | 
					@ -1,17 +1,3 @@
 | 
				
			||||||
// Copyright (C) 2023-present sam/u1f320 (vulpine.solutions)
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// This program is free software: you can redistribute it and/or modify
 | 
					 | 
				
			||||||
// it under the terms of the GNU Affero General Public License as published
 | 
					 | 
				
			||||||
// by the Free Software Foundation, either version 3 of the License, or
 | 
					 | 
				
			||||||
// (at your option) any later version.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// This program is distributed in the hope that it will be useful,
 | 
					 | 
				
			||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					 | 
				
			||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					 | 
				
			||||||
// GNU Affero General Public License for more details.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// You should have received a copy of the GNU Affero General Public License
 | 
					 | 
				
			||||||
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
					 | 
				
			||||||
using Foxnouns.Backend.Database;
 | 
					using Foxnouns.Backend.Database;
 | 
				
			||||||
using Foxnouns.Backend.Middleware;
 | 
					using Foxnouns.Backend.Middleware;
 | 
				
			||||||
using Microsoft.AspNetCore.Mvc;
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,8 +38,6 @@ public partial class InternalController(DatabaseContext db) : ControllerBase
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if (template.StartsWith("api/v2"))
 | 
					        if (template.StartsWith("api/v2"))
 | 
				
			||||||
            template = template["api/v2".Length..];
 | 
					            template = template["api/v2".Length..];
 | 
				
			||||||
        else if (template.StartsWith("api/v1"))
 | 
					 | 
				
			||||||
            template = template["api/v1".Length..];
 | 
					 | 
				
			||||||
        template = PathVarRegex()
 | 
					        template = PathVarRegex()
 | 
				
			||||||
            .Replace(template, "{id}") // Replace all path variables (almost always IDs) with `{id}`
 | 
					            .Replace(template, "{id}") // Replace all path variables (almost always IDs) with `{id}`
 | 
				
			||||||
            .Replace("@me", "{id}"); // Also replace hardcoded `@me` with `{id}`
 | 
					            .Replace("@me", "{id}"); // Also replace hardcoded `@me` with `{id}`
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,17 +1,3 @@
 | 
				
			||||||
// Copyright (C) 2023-present sam/u1f320 (vulpine.solutions)
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// This program is free software: you can redistribute it and/or modify
 | 
					 | 
				
			||||||
// it under the terms of the GNU Affero General Public License as published
 | 
					 | 
				
			||||||
// by the Free Software Foundation, either version 3 of the License, or
 | 
					 | 
				
			||||||
// (at your option) any later version.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// This program is distributed in the hope that it will be useful,
 | 
					 | 
				
			||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					 | 
				
			||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					 | 
				
			||||||
// GNU Affero General Public License for more details.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// You should have received a copy of the GNU Affero General Public License
 | 
					 | 
				
			||||||
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
					 | 
				
			||||||
using Foxnouns.Backend.Database;
 | 
					using Foxnouns.Backend.Database;
 | 
				
			||||||
using Foxnouns.Backend.Database.Models;
 | 
					using Foxnouns.Backend.Database.Models;
 | 
				
			||||||
using Foxnouns.Backend.Middleware;
 | 
					using Foxnouns.Backend.Middleware;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										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));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,111 +0,0 @@
 | 
				
			||||||
// Copyright (C) 2023-present sam/u1f320 (vulpine.solutions)
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// This program is free software: you can redistribute it and/or modify
 | 
					 | 
				
			||||||
// it under the terms of the GNU Affero General Public License as published
 | 
					 | 
				
			||||||
// by the Free Software Foundation, either version 3 of the License, or
 | 
					 | 
				
			||||||
// (at your option) any later version.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// This program is distributed in the hope that it will be useful,
 | 
					 | 
				
			||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					 | 
				
			||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					 | 
				
			||||||
// GNU Affero General Public License for more details.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// You should have received a copy of the GNU Affero General Public License
 | 
					 | 
				
			||||||
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
					 | 
				
			||||||
using Foxnouns.Backend.Database;
 | 
					 | 
				
			||||||
using Foxnouns.Backend.Database.Models;
 | 
					 | 
				
			||||||
using Foxnouns.Backend.Dto.V1;
 | 
					 | 
				
			||||||
using Foxnouns.Backend.Services.V1;
 | 
					 | 
				
			||||||
using Microsoft.AspNetCore.Mvc;
 | 
					 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace Foxnouns.Backend.Controllers.V1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[Route("/api/v1")]
 | 
					 | 
				
			||||||
public class V1ReadController(
 | 
					 | 
				
			||||||
    UsersV1Service usersV1Service,
 | 
					 | 
				
			||||||
    MembersV1Service membersV1Service,
 | 
					 | 
				
			||||||
    DatabaseContext db
 | 
					 | 
				
			||||||
) : ApiControllerBase
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    [HttpGet("users/{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,
 | 
					 | 
				
			||||||
                CurrentToken,
 | 
					 | 
				
			||||||
                renderMembers: true,
 | 
					 | 
				
			||||||
                renderFlags: true,
 | 
					 | 
				
			||||||
                ct: ct
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [HttpGet("members/{id}")]
 | 
					 | 
				
			||||||
    public async Task<IActionResult> GetMemberAsync(string id, CancellationToken ct = default)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        Member member = await membersV1Service.ResolveMemberAsync(id, ct);
 | 
					 | 
				
			||||||
        return Ok(
 | 
					 | 
				
			||||||
            await membersV1Service.RenderMemberAsync(
 | 
					 | 
				
			||||||
                member,
 | 
					 | 
				
			||||||
                CurrentToken,
 | 
					 | 
				
			||||||
                renderFlags: true,
 | 
					 | 
				
			||||||
                ct: ct
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [HttpGet("users/{userRef}/members")]
 | 
					 | 
				
			||||||
    public async Task<IActionResult> GetUserMembersAsync(
 | 
					 | 
				
			||||||
        string userRef,
 | 
					 | 
				
			||||||
        CancellationToken ct = default
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        User user = await usersV1Service.ResolveUserAsync(userRef, CurrentToken, ct);
 | 
					 | 
				
			||||||
        List<Member> members = await db
 | 
					 | 
				
			||||||
            .Members.Where(m => m.UserId == user.Id)
 | 
					 | 
				
			||||||
            .OrderBy(m => m.Name)
 | 
					 | 
				
			||||||
            .ToListAsync(ct);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        List<MemberResponse> responses = [];
 | 
					 | 
				
			||||||
        foreach (Member member in members)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            responses.Add(
 | 
					 | 
				
			||||||
                await membersV1Service.RenderMemberAsync(
 | 
					 | 
				
			||||||
                    member,
 | 
					 | 
				
			||||||
                    CurrentToken,
 | 
					 | 
				
			||||||
                    user,
 | 
					 | 
				
			||||||
                    renderFlags: true,
 | 
					 | 
				
			||||||
                    ct: ct
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return Ok(responses);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [HttpGet("users/{userRef}/members/{memberRef}")]
 | 
					 | 
				
			||||||
    public async Task<IActionResult> GetUserMemberAsync(
 | 
					 | 
				
			||||||
        string userRef,
 | 
					 | 
				
			||||||
        string memberRef,
 | 
					 | 
				
			||||||
        CancellationToken ct = default
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        Member member = await membersV1Service.ResolveMemberAsync(
 | 
					 | 
				
			||||||
            userRef,
 | 
					 | 
				
			||||||
            memberRef,
 | 
					 | 
				
			||||||
            CurrentToken,
 | 
					 | 
				
			||||||
            ct
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        return Ok(
 | 
					 | 
				
			||||||
            await membersV1Service.RenderMemberAsync(
 | 
					 | 
				
			||||||
                member,
 | 
					 | 
				
			||||||
                CurrentToken,
 | 
					 | 
				
			||||||
                renderFlags: true,
 | 
					 | 
				
			||||||
                ct: ct
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,59 +0,0 @@
 | 
				
			||||||
// Copyright (C) 2023-present sam/u1f320 (vulpine.solutions)
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// This program is free software: you can redistribute it and/or modify
 | 
					 | 
				
			||||||
// it under the terms of the GNU Affero General Public License as published
 | 
					 | 
				
			||||||
// by the Free Software Foundation, either version 3 of the License, or
 | 
					 | 
				
			||||||
// (at your option) any later version.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// This program is distributed in the hope that it will be useful,
 | 
					 | 
				
			||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					 | 
				
			||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					 | 
				
			||||||
// GNU Affero General Public License for more details.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// You should have received a copy of the GNU Affero General Public License
 | 
					 | 
				
			||||||
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ReSharper disable NotAccessedPositionalProperty.Global
 | 
					 | 
				
			||||||
using Foxnouns.Backend.Database;
 | 
					 | 
				
			||||||
using Newtonsoft.Json;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace Foxnouns.Backend.Dto.V1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public record PartialMember(
 | 
					 | 
				
			||||||
    string Id,
 | 
					 | 
				
			||||||
    Snowflake IdNew,
 | 
					 | 
				
			||||||
    string Sid,
 | 
					 | 
				
			||||||
    string Name,
 | 
					 | 
				
			||||||
    string? DisplayName,
 | 
					 | 
				
			||||||
    string? Bio,
 | 
					 | 
				
			||||||
    string? Avatar,
 | 
					 | 
				
			||||||
    string[] Links,
 | 
					 | 
				
			||||||
    FieldEntry[] Names,
 | 
					 | 
				
			||||||
    PronounEntry[] Pronouns
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public record MemberResponse(
 | 
					 | 
				
			||||||
    string Id,
 | 
					 | 
				
			||||||
    Snowflake IdNew,
 | 
					 | 
				
			||||||
    string Sid,
 | 
					 | 
				
			||||||
    string Name,
 | 
					 | 
				
			||||||
    string? DisplayName,
 | 
					 | 
				
			||||||
    string? Bio,
 | 
					 | 
				
			||||||
    string? Avatar,
 | 
					 | 
				
			||||||
    string[] Links,
 | 
					 | 
				
			||||||
    FieldEntry[] Names,
 | 
					 | 
				
			||||||
    PronounEntry[] Pronouns,
 | 
					 | 
				
			||||||
    ProfileField[] Fields,
 | 
					 | 
				
			||||||
    PrideFlag[] Flags,
 | 
					 | 
				
			||||||
    PartialUser User,
 | 
					 | 
				
			||||||
    [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] bool? Unlisted
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public record PartialUser(
 | 
					 | 
				
			||||||
    string Id,
 | 
					 | 
				
			||||||
    Snowflake IdNew,
 | 
					 | 
				
			||||||
    string Name,
 | 
					 | 
				
			||||||
    string? DisplayName,
 | 
					 | 
				
			||||||
    string? Avatar,
 | 
					 | 
				
			||||||
    Dictionary<Guid, CustomPreference> CustomPreferences
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,18 +1,3 @@
 | 
				
			||||||
// Copyright (C) 2023-present sam/u1f320 (vulpine.solutions)
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// This program is free software: you can redistribute it and/or modify
 | 
					 | 
				
			||||||
// it under the terms of the GNU Affero General Public License as published
 | 
					 | 
				
			||||||
// by the Free Software Foundation, either version 3 of the License, or
 | 
					 | 
				
			||||||
// (at your option) any later version.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// This program is distributed in the hope that it will be useful,
 | 
					 | 
				
			||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					 | 
				
			||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					 | 
				
			||||||
// GNU Affero General Public License for more details.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// You should have received a copy of the GNU Affero General Public License
 | 
					 | 
				
			||||||
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ReSharper disable NotAccessedPositionalProperty.Global
 | 
					// ReSharper disable NotAccessedPositionalProperty.Global
 | 
				
			||||||
using Foxnouns.Backend.Database;
 | 
					using Foxnouns.Backend.Database;
 | 
				
			||||||
using Foxnouns.Backend.Database.Models;
 | 
					using Foxnouns.Backend.Database.Models;
 | 
				
			||||||
| 
						 | 
					@ -36,8 +21,6 @@ public record UserResponse(
 | 
				
			||||||
    FieldEntry[] Names,
 | 
					    FieldEntry[] Names,
 | 
				
			||||||
    PronounEntry[] Pronouns,
 | 
					    PronounEntry[] Pronouns,
 | 
				
			||||||
    ProfileField[] Fields,
 | 
					    ProfileField[] Fields,
 | 
				
			||||||
    PrideFlag[] Flags,
 | 
					 | 
				
			||||||
    PartialMember[] Members,
 | 
					 | 
				
			||||||
    int? UtcOffset,
 | 
					    int? UtcOffset,
 | 
				
			||||||
    Dictionary<Guid, CustomPreference> CustomPreferences
 | 
					    Dictionary<Guid, CustomPreference> CustomPreferences
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
| 
						 | 
					@ -92,5 +75,3 @@ public record PronounEntry(string Pronouns, string? DisplayText, string Status)
 | 
				
			||||||
            ))
 | 
					            ))
 | 
				
			||||||
            .ToArray();
 | 
					            .ToArray();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
public record PrideFlag(string Id, Snowflake IdNew, string Hash, string Name, string? Description);
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -130,8 +130,7 @@ public static class WebApplicationExtensions
 | 
				
			||||||
                    .AddTransient<CreateFlagInvocable>()
 | 
					                    .AddTransient<CreateFlagInvocable>()
 | 
				
			||||||
                    .AddTransient<CreateDataExportInvocable>()
 | 
					                    .AddTransient<CreateDataExportInvocable>()
 | 
				
			||||||
                    // Legacy services
 | 
					                    // Legacy services
 | 
				
			||||||
                    .AddScoped<UsersV1Service>()
 | 
					                    .AddScoped<UsersV1Service>();
 | 
				
			||||||
                    .AddScoped<MembersV1Service>();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (!config.Logging.EnableMetrics)
 | 
					                if (!config.Logging.EnableMetrics)
 | 
				
			||||||
                    services.AddHostedService<BackgroundMetricsCollectionService>();
 | 
					                    services.AddHostedService<BackgroundMetricsCollectionService>();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,125 +0,0 @@
 | 
				
			||||||
// Copyright (C) 2023-present sam/u1f320 (vulpine.solutions)
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// This program is free software: you can redistribute it and/or modify
 | 
					 | 
				
			||||||
// it under the terms of the GNU Affero General Public License as published
 | 
					 | 
				
			||||||
// by the Free Software Foundation, either version 3 of the License, or
 | 
					 | 
				
			||||||
// (at your option) any later version.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// This program is distributed in the hope that it will be useful,
 | 
					 | 
				
			||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					 | 
				
			||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					 | 
				
			||||||
// GNU Affero General Public License for more details.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// You should have received a copy of the GNU Affero General Public License
 | 
					 | 
				
			||||||
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
					 | 
				
			||||||
using Foxnouns.Backend.Database;
 | 
					 | 
				
			||||||
using Foxnouns.Backend.Database.Models;
 | 
					 | 
				
			||||||
using Foxnouns.Backend.Dto.V1;
 | 
					 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					 | 
				
			||||||
using FieldEntry = Foxnouns.Backend.Dto.V1.FieldEntry;
 | 
					 | 
				
			||||||
using PrideFlag = Foxnouns.Backend.Dto.V1.PrideFlag;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace Foxnouns.Backend.Services.V1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class MembersV1Service(DatabaseContext db, UsersV1Service usersV1Service)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    public async Task<Member> ResolveMemberAsync(string id, CancellationToken ct = default)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        Member? member;
 | 
					 | 
				
			||||||
        if (Snowflake.TryParse(id, out Snowflake? sf))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            member = await db
 | 
					 | 
				
			||||||
                .Members.Include(m => m.User)
 | 
					 | 
				
			||||||
                .FirstOrDefaultAsync(m => m.Id == sf && !m.User.Deleted, ct);
 | 
					 | 
				
			||||||
            if (member != null)
 | 
					 | 
				
			||||||
                return member;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        member = await db
 | 
					 | 
				
			||||||
            .Members.Include(m => m.User)
 | 
					 | 
				
			||||||
            .FirstOrDefaultAsync(m => m.LegacyId == id && !m.User.Deleted, ct);
 | 
					 | 
				
			||||||
        if (member != null)
 | 
					 | 
				
			||||||
            return member;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        throw new ApiError.NotFound("No member with that ID found.", ErrorCode.MemberNotFound);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public async Task<Member> ResolveMemberAsync(
 | 
					 | 
				
			||||||
        string userRef,
 | 
					 | 
				
			||||||
        string memberRef,
 | 
					 | 
				
			||||||
        Token? token,
 | 
					 | 
				
			||||||
        CancellationToken ct = default
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        User user = await usersV1Service.ResolveUserAsync(userRef, token, ct);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Member? member;
 | 
					 | 
				
			||||||
        if (Snowflake.TryParse(memberRef, out Snowflake? sf))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            member = await db
 | 
					 | 
				
			||||||
                .Members.Include(m => m.User)
 | 
					 | 
				
			||||||
                .FirstOrDefaultAsync(m => m.Id == sf && m.UserId == user.Id, ct);
 | 
					 | 
				
			||||||
            if (member != null)
 | 
					 | 
				
			||||||
                return member;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        member = await db
 | 
					 | 
				
			||||||
            .Members.Include(m => m.User)
 | 
					 | 
				
			||||||
            .FirstOrDefaultAsync(m => m.LegacyId == memberRef && m.UserId == user.Id, ct);
 | 
					 | 
				
			||||||
        if (member != null)
 | 
					 | 
				
			||||||
            return member;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        member = await db
 | 
					 | 
				
			||||||
            .Members.Include(m => m.User)
 | 
					 | 
				
			||||||
            .FirstOrDefaultAsync(m => m.Name == memberRef && m.UserId == user.Id, ct);
 | 
					 | 
				
			||||||
        if (member != null)
 | 
					 | 
				
			||||||
            return member;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        throw new ApiError.NotFound(
 | 
					 | 
				
			||||||
            "No member with that ID or name found.",
 | 
					 | 
				
			||||||
            ErrorCode.MemberNotFound
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public async Task<MemberResponse> RenderMemberAsync(
 | 
					 | 
				
			||||||
        Member m,
 | 
					 | 
				
			||||||
        Token? token = default,
 | 
					 | 
				
			||||||
        User? user = null,
 | 
					 | 
				
			||||||
        bool renderFlags = true,
 | 
					 | 
				
			||||||
        CancellationToken ct = default
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        user ??= m.User;
 | 
					 | 
				
			||||||
        bool renderUnlisted = m.UserId == token?.UserId;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        List<MemberFlag> flags = renderFlags
 | 
					 | 
				
			||||||
            ? await db.MemberFlags.Where(f => f.MemberId == m.Id).OrderBy(f => f.Id).ToListAsync(ct)
 | 
					 | 
				
			||||||
            : [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return new MemberResponse(
 | 
					 | 
				
			||||||
            m.LegacyId,
 | 
					 | 
				
			||||||
            m.Id,
 | 
					 | 
				
			||||||
            m.Sid,
 | 
					 | 
				
			||||||
            m.Name,
 | 
					 | 
				
			||||||
            m.DisplayName,
 | 
					 | 
				
			||||||
            m.Bio,
 | 
					 | 
				
			||||||
            m.Avatar,
 | 
					 | 
				
			||||||
            m.Links,
 | 
					 | 
				
			||||||
            Names: FieldEntry.FromEntries(m.Names, user.CustomPreferences),
 | 
					 | 
				
			||||||
            Pronouns: PronounEntry.FromPronouns(m.Pronouns, user.CustomPreferences),
 | 
					 | 
				
			||||||
            Fields: ProfileField.FromFields(m.Fields, user.CustomPreferences),
 | 
					 | 
				
			||||||
            Flags: flags
 | 
					 | 
				
			||||||
                .Where(f => f.PrideFlag.Hash != null)
 | 
					 | 
				
			||||||
                .Select(f => new PrideFlag(
 | 
					 | 
				
			||||||
                    f.PrideFlag.LegacyId,
 | 
					 | 
				
			||||||
                    f.PrideFlag.Id,
 | 
					 | 
				
			||||||
                    f.PrideFlag.Hash!,
 | 
					 | 
				
			||||||
                    f.PrideFlag.Name,
 | 
					 | 
				
			||||||
                    f.PrideFlag.Description
 | 
					 | 
				
			||||||
                ))
 | 
					 | 
				
			||||||
                .ToArray(),
 | 
					 | 
				
			||||||
            User: UsersV1Service.RenderPartialUser(user),
 | 
					 | 
				
			||||||
            Unlisted: renderUnlisted ? m.Unlisted : null
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,23 +1,8 @@
 | 
				
			||||||
// Copyright (C) 2023-present sam/u1f320 (vulpine.solutions)
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// This program is free software: you can redistribute it and/or modify
 | 
					 | 
				
			||||||
// it under the terms of the GNU Affero General Public License as published
 | 
					 | 
				
			||||||
// by the Free Software Foundation, either version 3 of the License, or
 | 
					 | 
				
			||||||
// (at your option) any later version.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// This program is distributed in the hope that it will be useful,
 | 
					 | 
				
			||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					 | 
				
			||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					 | 
				
			||||||
// GNU Affero General Public License for more details.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// You should have received a copy of the GNU Affero General Public License
 | 
					 | 
				
			||||||
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
					 | 
				
			||||||
using Foxnouns.Backend.Database;
 | 
					using Foxnouns.Backend.Database;
 | 
				
			||||||
using Foxnouns.Backend.Database.Models;
 | 
					using Foxnouns.Backend.Database.Models;
 | 
				
			||||||
using Foxnouns.Backend.Dto.V1;
 | 
					using Foxnouns.Backend.Dto.V1;
 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
using FieldEntry = Foxnouns.Backend.Dto.V1.FieldEntry;
 | 
					using FieldEntry = Foxnouns.Backend.Dto.V1.FieldEntry;
 | 
				
			||||||
using PrideFlag = Foxnouns.Backend.Dto.V1.PrideFlag;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Foxnouns.Backend.Services.V1;
 | 
					namespace Foxnouns.Backend.Services.V1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -64,26 +49,8 @@ public class UsersV1Service(DatabaseContext db)
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public async Task<UserResponse> RenderUserAsync(
 | 
					    public async Task<UserResponse> RenderUserAsync(User user)
 | 
				
			||||||
        User user,
 | 
					 | 
				
			||||||
        Token? token = null,
 | 
					 | 
				
			||||||
        bool renderMembers = true,
 | 
					 | 
				
			||||||
        bool renderFlags = true,
 | 
					 | 
				
			||||||
        CancellationToken ct = default
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        bool isSelfUser = user.Id == token?.UserId;
 | 
					 | 
				
			||||||
        renderMembers = renderMembers && (isSelfUser || !user.ListHidden);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Only fetch members if we're rendering members (duh)
 | 
					 | 
				
			||||||
        List<Member> members = renderMembers
 | 
					 | 
				
			||||||
            ? await db.Members.Where(m => m.UserId == user.Id).OrderBy(m => m.Name).ToListAsync(ct)
 | 
					 | 
				
			||||||
            : [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        List<UserFlag> flags = renderFlags
 | 
					 | 
				
			||||||
            ? await db.UserFlags.Where(f => f.UserId == user.Id).OrderBy(f => f.Id).ToListAsync(ct)
 | 
					 | 
				
			||||||
            : [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        int? utcOffset = null;
 | 
					        int? utcOffset = null;
 | 
				
			||||||
        if (
 | 
					        if (
 | 
				
			||||||
            user.Timezone != null
 | 
					            user.Timezone != null
 | 
				
			||||||
| 
						 | 
					@ -103,30 +70,11 @@ public class UsersV1Service(DatabaseContext db)
 | 
				
			||||||
            user.MemberTitle,
 | 
					            user.MemberTitle,
 | 
				
			||||||
            user.Avatar,
 | 
					            user.Avatar,
 | 
				
			||||||
            user.Links,
 | 
					            user.Links,
 | 
				
			||||||
            Names: FieldEntry.FromEntries(user.Names, user.CustomPreferences),
 | 
					            FieldEntry.FromEntries(user.Names, user.CustomPreferences),
 | 
				
			||||||
            Pronouns: PronounEntry.FromPronouns(user.Pronouns, user.CustomPreferences),
 | 
					            PronounEntry.FromPronouns(user.Pronouns, user.CustomPreferences),
 | 
				
			||||||
            Fields: ProfileField.FromFields(user.Fields, user.CustomPreferences),
 | 
					            ProfileField.FromFields(user.Fields, user.CustomPreferences),
 | 
				
			||||||
            Flags: flags
 | 
					 | 
				
			||||||
                .Where(f => f.PrideFlag.Hash != null)
 | 
					 | 
				
			||||||
                .Select(f => new PrideFlag(
 | 
					 | 
				
			||||||
                    f.PrideFlag.LegacyId,
 | 
					 | 
				
			||||||
                    f.PrideFlag.Id,
 | 
					 | 
				
			||||||
                    f.PrideFlag.Hash!,
 | 
					 | 
				
			||||||
                    f.PrideFlag.Name,
 | 
					 | 
				
			||||||
                    f.PrideFlag.Description
 | 
					 | 
				
			||||||
                ))
 | 
					 | 
				
			||||||
                .ToArray(),
 | 
					 | 
				
			||||||
            Members: members.Select(m => RenderPartialMember(m, user.CustomPreferences)).ToArray(),
 | 
					 | 
				
			||||||
            utcOffset,
 | 
					            utcOffset,
 | 
				
			||||||
            CustomPreferences: RenderCustomPreferences(user.CustomPreferences)
 | 
					            user.CustomPreferences.Select(x =>
 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private static Dictionary<Guid, CustomPreference> RenderCustomPreferences(
 | 
					 | 
				
			||||||
        Dictionary<Snowflake, User.CustomPreference> customPreferences
 | 
					 | 
				
			||||||
    ) =>
 | 
					 | 
				
			||||||
        customPreferences
 | 
					 | 
				
			||||||
            .Select(x =>
 | 
					 | 
				
			||||||
                    (
 | 
					                    (
 | 
				
			||||||
                        x.Value.LegacyId,
 | 
					                        x.Value.LegacyId,
 | 
				
			||||||
                        new CustomPreference(
 | 
					                        new CustomPreference(
 | 
				
			||||||
| 
						 | 
					@ -138,32 +86,7 @@ public class UsersV1Service(DatabaseContext db)
 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            .ToDictionary();
 | 
					                .ToDictionary()
 | 
				
			||||||
 | 
					 | 
				
			||||||
    private static PartialMember RenderPartialMember(
 | 
					 | 
				
			||||||
        Member m,
 | 
					 | 
				
			||||||
        Dictionary<Snowflake, User.CustomPreference> customPreferences
 | 
					 | 
				
			||||||
    ) =>
 | 
					 | 
				
			||||||
        new(
 | 
					 | 
				
			||||||
            m.LegacyId,
 | 
					 | 
				
			||||||
            m.Id,
 | 
					 | 
				
			||||||
            m.Sid,
 | 
					 | 
				
			||||||
            m.Name,
 | 
					 | 
				
			||||||
            m.DisplayName,
 | 
					 | 
				
			||||||
            m.Bio,
 | 
					 | 
				
			||||||
            m.Avatar,
 | 
					 | 
				
			||||||
            m.Links,
 | 
					 | 
				
			||||||
            Names: FieldEntry.FromEntries(m.Names, customPreferences),
 | 
					 | 
				
			||||||
            Pronouns: PronounEntry.FromPronouns(m.Pronouns, customPreferences)
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static PartialUser RenderPartialUser(User user) =>
 | 
					 | 
				
			||||||
        new(
 | 
					 | 
				
			||||||
            user.LegacyId,
 | 
					 | 
				
			||||||
            user.Id,
 | 
					 | 
				
			||||||
            user.Username,
 | 
					 | 
				
			||||||
            user.DisplayName,
 | 
					 | 
				
			||||||
            user.Avatar,
 | 
					 | 
				
			||||||
            CustomPreferences: RenderCustomPreferences(user.CustomPreferences)
 | 
					 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,17 +1,3 @@
 | 
				
			||||||
// Copyright (C) 2023-present sam/u1f320 (vulpine.solutions)
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// This program is free software: you can redistribute it and/or modify
 | 
					 | 
				
			||||||
// it under the terms of the GNU Affero General Public License as published
 | 
					 | 
				
			||||||
// by the Free Software Foundation, either version 3 of the License, or
 | 
					 | 
				
			||||||
// (at your option) any later version.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// This program is distributed in the hope that it will be useful,
 | 
					 | 
				
			||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					 | 
				
			||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					 | 
				
			||||||
// GNU Affero General Public License for more details.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// You should have received a copy of the GNU Affero General Public License
 | 
					 | 
				
			||||||
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
					 | 
				
			||||||
using Foxnouns.Backend.Database;
 | 
					using Foxnouns.Backend.Database;
 | 
				
			||||||
using Foxnouns.Backend.Database.Models;
 | 
					using Foxnouns.Backend.Database.Models;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
; The host the server will listen on
 | 
					; The host the server will listen on
 | 
				
			||||||
Host = localhost
 | 
					Host = localhost
 | 
				
			||||||
; The port the server will listen on
 | 
					; The port the server will listen on
 | 
				
			||||||
Port = 6000
 | 
					Port = 5000
 | 
				
			||||||
; The base *external* URL
 | 
					; The base *external* URL
 | 
				
			||||||
BaseUrl = https://pronouns.localhost
 | 
					BaseUrl = https://pronouns.localhost
 | 
				
			||||||
; The base URL for media, without a trailing slash. This must be publicly accessible.
 | 
					; The base URL for media, without a trailing slash. This must be publicly accessible.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
# Example .env file--DO NOT EDIT, copy to .env or .env.local then edit
 | 
					# Example .env file--DO NOT EDIT
 | 
				
			||||||
PUBLIC_LANGUAGE=en
 | 
					PUBLIC_LANGUAGE=en
 | 
				
			||||||
PUBLIC_BASE_URL=https://pronouns.cc
 | 
					PUBLIC_BASE_URL=https://pronouns.cc
 | 
				
			||||||
PUBLIC_SHORT_URL=https://prns.cc
 | 
					PUBLIC_SHORT_URL=https://prns.cc
 | 
				
			||||||
PUBLIC_API_BASE=https://pronouns.cc/api
 | 
					PUBLIC_API_BASE=https://pronouns.cc/api
 | 
				
			||||||
PRIVATE_API_HOST=http://localhost:5003/api
 | 
					PRIVATE_API_HOST=http://localhost:5003/api
 | 
				
			||||||
PRIVATE_INTERNAL_API_HOST=http://localhost:6000/api
 | 
					PRIVATE_INTERNAL_API_HOST=http://localhost:5000/api
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,7 +38,7 @@ func (hn *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// all public api endpoints are prefixed with this
 | 
						// all public api endpoints are prefixed with this
 | 
				
			||||||
	if !strings.HasPrefix(r.URL.Path, "/api/v2") && !strings.HasPrefix(r.URL.Path, "/api/v1") {
 | 
						if !strings.HasPrefix(r.URL.Path, "/api/v2") {
 | 
				
			||||||
		w.WriteHeader(http.StatusNotFound)
 | 
							w.WriteHeader(http.StatusNotFound)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    "port": 5003,
 | 
					    "port": 5003,
 | 
				
			||||||
    "proxy_target": "http://localhost:6000",
 | 
					    "proxy_target": "http://localhost:5000",
 | 
				
			||||||
    "debug": true,
 | 
					    "debug": true,
 | 
				
			||||||
    "powered_by": "5 gay rats"
 | 
					    "powered_by": "5 gay rats"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue