Compare commits
No commits in common. "7791c9196091d9c6bbeb2fda46f0913b925cc4b7" and "e24c4f9b0033fc82092a7c5c87bd56792d549cb6" have entirely different histories.
7791c91960
...
e24c4f9b00
23 changed files with 7 additions and 484 deletions
|
@ -22,7 +22,6 @@ using Foxnouns.Backend.Services;
|
||||||
using Foxnouns.Backend.Utils;
|
using Foxnouns.Backend.Utils;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using XidNet;
|
|
||||||
|
|
||||||
namespace Foxnouns.Backend.Controllers;
|
namespace Foxnouns.Backend.Controllers;
|
||||||
|
|
||||||
|
@ -65,7 +64,6 @@ public class FlagsController(
|
||||||
var flag = new PrideFlag
|
var flag = new PrideFlag
|
||||||
{
|
{
|
||||||
Id = snowflakeGenerator.GenerateSnowflake(),
|
Id = snowflakeGenerator.GenerateSnowflake(),
|
||||||
LegacyId = Xid.NewXid().ToString(),
|
|
||||||
UserId = CurrentUser!.Id,
|
UserId = CurrentUser!.Id,
|
||||||
Name = req.Name,
|
Name = req.Name,
|
||||||
Description = req.Description,
|
Description = req.Description,
|
||||||
|
|
|
@ -26,7 +26,6 @@ using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Storage;
|
using Microsoft.EntityFrameworkCore.Storage;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using XidNet;
|
|
||||||
|
|
||||||
namespace Foxnouns.Backend.Controllers;
|
namespace Foxnouns.Backend.Controllers;
|
||||||
|
|
||||||
|
@ -102,7 +101,6 @@ public class MembersController(
|
||||||
var member = new Member
|
var member = new Member
|
||||||
{
|
{
|
||||||
Id = snowflakeGenerator.GenerateSnowflake(),
|
Id = snowflakeGenerator.GenerateSnowflake(),
|
||||||
LegacyId = Xid.NewXid().ToString(),
|
|
||||||
User = CurrentUser!,
|
User = CurrentUser!,
|
||||||
Name = req.Name,
|
Name = req.Name,
|
||||||
DisplayName = req.DisplayName,
|
DisplayName = req.DisplayName,
|
||||||
|
|
|
@ -222,7 +222,7 @@ public class UsersController(
|
||||||
.CustomPreferences.Where(x => req.Any(r => r.Id == x.Key))
|
.CustomPreferences.Where(x => req.Any(r => r.Id == x.Key))
|
||||||
.ToDictionary();
|
.ToDictionary();
|
||||||
|
|
||||||
foreach (CustomPreferenceUpdateRequest r in req)
|
foreach (CustomPreferenceUpdateRequest? r in req)
|
||||||
{
|
{
|
||||||
if (r.Id != null && preferences.ContainsKey(r.Id.Value))
|
if (r.Id != null && preferences.ContainsKey(r.Id.Value))
|
||||||
{
|
{
|
||||||
|
@ -233,7 +233,6 @@ public class UsersController(
|
||||||
Muted = r.Muted,
|
Muted = r.Muted,
|
||||||
Size = r.Size,
|
Size = r.Size,
|
||||||
Tooltip = r.Tooltip,
|
Tooltip = r.Tooltip,
|
||||||
LegacyId = preferences[r.Id.Value].LegacyId,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -245,7 +244,6 @@ public class UsersController(
|
||||||
Muted = r.Muted,
|
Muted = r.Muted,
|
||||||
Size = r.Size,
|
Size = r.Size,
|
||||||
Tooltip = r.Tooltip,
|
Tooltip = r.Tooltip,
|
||||||
LegacyId = Guid.NewGuid(),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
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,26 +139,6 @@ public class DatabaseContext(DbContextOptions options) : DbContext(options)
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasDbFunction(typeof(DatabaseContext).GetMethod(nameof(FindFreeMemberSid))!)
|
.HasDbFunction(typeof(DatabaseContext).GetMethod(nameof(FindFreeMemberSid))!)
|
||||||
.HasName("find_free_member_sid");
|
.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>
|
/// <summary>
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
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,13 +254,6 @@ namespace Foxnouns.Backend.Database.Migrations
|
||||||
.HasColumnType("jsonb")
|
.HasColumnType("jsonb")
|
||||||
.HasColumnName("fields");
|
.HasColumnName("fields");
|
||||||
|
|
||||||
b.Property<string>("LegacyId")
|
|
||||||
.IsRequired()
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("text")
|
|
||||||
.HasColumnName("legacy_id")
|
|
||||||
.HasDefaultValueSql("gen_random_uuid()");
|
|
||||||
|
|
||||||
b.PrimitiveCollection<string[]>("Links")
|
b.PrimitiveCollection<string[]>("Links")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text[]")
|
.HasColumnType("text[]")
|
||||||
|
@ -299,10 +292,6 @@ namespace Foxnouns.Backend.Database.Migrations
|
||||||
b.HasKey("Id")
|
b.HasKey("Id")
|
||||||
.HasName("pk_members");
|
.HasName("pk_members");
|
||||||
|
|
||||||
b.HasIndex("LegacyId")
|
|
||||||
.IsUnique()
|
|
||||||
.HasDatabaseName("ix_members_legacy_id");
|
|
||||||
|
|
||||||
b.HasIndex("Sid")
|
b.HasIndex("Sid")
|
||||||
.IsUnique()
|
.IsUnique()
|
||||||
.HasDatabaseName("ix_members_sid");
|
.HasDatabaseName("ix_members_sid");
|
||||||
|
@ -397,13 +386,6 @@ namespace Foxnouns.Backend.Database.Migrations
|
||||||
.HasColumnType("text")
|
.HasColumnType("text")
|
||||||
.HasColumnName("hash");
|
.HasColumnName("hash");
|
||||||
|
|
||||||
b.Property<string>("LegacyId")
|
|
||||||
.IsRequired()
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("text")
|
|
||||||
.HasColumnName("legacy_id")
|
|
||||||
.HasDefaultValueSql("gen_random_uuid()");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text")
|
.HasColumnType("text")
|
||||||
|
@ -416,10 +398,6 @@ namespace Foxnouns.Backend.Database.Migrations
|
||||||
b.HasKey("Id")
|
b.HasKey("Id")
|
||||||
.HasName("pk_pride_flags");
|
.HasName("pk_pride_flags");
|
||||||
|
|
||||||
b.HasIndex("LegacyId")
|
|
||||||
.IsUnique()
|
|
||||||
.HasDatabaseName("ix_pride_flags_legacy_id");
|
|
||||||
|
|
||||||
b.HasIndex("UserId")
|
b.HasIndex("UserId")
|
||||||
.HasDatabaseName("ix_pride_flags_user_id");
|
.HasDatabaseName("ix_pride_flags_user_id");
|
||||||
|
|
||||||
|
@ -604,13 +582,6 @@ namespace Foxnouns.Backend.Database.Migrations
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("last_sid_reroll");
|
.HasColumnName("last_sid_reroll");
|
||||||
|
|
||||||
b.Property<string>("LegacyId")
|
|
||||||
.IsRequired()
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("text")
|
|
||||||
.HasColumnName("legacy_id")
|
|
||||||
.HasDefaultValueSql("gen_random_uuid()");
|
|
||||||
|
|
||||||
b.PrimitiveCollection<string[]>("Links")
|
b.PrimitiveCollection<string[]>("Links")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text[]")
|
.HasColumnType("text[]")
|
||||||
|
@ -666,10 +637,6 @@ namespace Foxnouns.Backend.Database.Migrations
|
||||||
b.HasKey("Id")
|
b.HasKey("Id")
|
||||||
.HasName("pk_users");
|
.HasName("pk_users");
|
||||||
|
|
||||||
b.HasIndex("LegacyId")
|
|
||||||
.IsUnique()
|
|
||||||
.HasDatabaseName("ix_users_legacy_id");
|
|
||||||
|
|
||||||
b.HasIndex("Sid")
|
b.HasIndex("Sid")
|
||||||
.IsUnique()
|
.IsUnique()
|
||||||
.HasDatabaseName("ix_users_sid");
|
.HasDatabaseName("ix_users_sid");
|
||||||
|
|
|
@ -18,7 +18,6 @@ public class Member : BaseModel
|
||||||
{
|
{
|
||||||
public required string Name { get; set; }
|
public required string Name { get; set; }
|
||||||
public string Sid { get; set; } = string.Empty;
|
public string Sid { get; set; } = string.Empty;
|
||||||
public required string LegacyId { get; init; }
|
|
||||||
public string? DisplayName { get; set; }
|
public string? DisplayName { get; set; }
|
||||||
public string? Bio { get; set; }
|
public string? Bio { get; set; }
|
||||||
public string? Avatar { get; set; }
|
public string? Avatar { get; set; }
|
||||||
|
|
|
@ -17,7 +17,6 @@ namespace Foxnouns.Backend.Database.Models;
|
||||||
public class PrideFlag : BaseModel
|
public class PrideFlag : BaseModel
|
||||||
{
|
{
|
||||||
public required Snowflake UserId { get; init; }
|
public required Snowflake UserId { get; init; }
|
||||||
public required string LegacyId { get; init; }
|
|
||||||
|
|
||||||
// A null hash means the flag hasn't been processed yet.
|
// A null hash means the flag hasn't been processed yet.
|
||||||
public string? Hash { get; set; }
|
public string? Hash { get; set; }
|
||||||
|
|
|
@ -25,7 +25,6 @@ public class User : BaseModel
|
||||||
{
|
{
|
||||||
public required string Username { get; set; }
|
public required string Username { get; set; }
|
||||||
public string Sid { get; set; } = string.Empty;
|
public string Sid { get; set; } = string.Empty;
|
||||||
public required string LegacyId { get; init; }
|
|
||||||
public string? DisplayName { get; set; }
|
public string? DisplayName { get; set; }
|
||||||
public string? Bio { get; set; }
|
public string? Bio { get; set; }
|
||||||
public string? MemberTitle { get; set; }
|
public string? MemberTitle { get; set; }
|
||||||
|
@ -70,8 +69,6 @@ public class User : BaseModel
|
||||||
// This type is generally serialized directly, so the converter is applied here.
|
// This type is generally serialized directly, so the converter is applied here.
|
||||||
[JsonConverter(typeof(ScreamingSnakeCaseEnumConverter))]
|
[JsonConverter(typeof(ScreamingSnakeCaseEnumConverter))]
|
||||||
public PreferenceSize Size { get; set; }
|
public PreferenceSize Size { get; set; }
|
||||||
|
|
||||||
public Guid LegacyId { get; init; } = Guid.NewGuid();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly Duration DeleteAfter = Duration.FromDays(30);
|
public static readonly Duration DeleteAfter = Duration.FromDays(30);
|
||||||
|
|
|
@ -36,7 +36,7 @@ public record UserResponse(
|
||||||
IEnumerable<FieldEntry> Names,
|
IEnumerable<FieldEntry> Names,
|
||||||
IEnumerable<Pronoun> Pronouns,
|
IEnumerable<Pronoun> Pronouns,
|
||||||
IEnumerable<Field> Fields,
|
IEnumerable<Field> Fields,
|
||||||
Dictionary<Snowflake, CustomPreferenceResponse> CustomPreferences,
|
Dictionary<Snowflake, User.CustomPreference> CustomPreferences,
|
||||||
IEnumerable<PrideFlagResponse> Flags,
|
IEnumerable<PrideFlagResponse> Flags,
|
||||||
int? UtcOffset,
|
int? UtcOffset,
|
||||||
[property: JsonConverter(typeof(ScreamingSnakeCaseEnumConverter))] UserRole Role,
|
[property: JsonConverter(typeof(ScreamingSnakeCaseEnumConverter))] UserRole Role,
|
||||||
|
@ -52,14 +52,6 @@ public record UserResponse(
|
||||||
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] bool? Deleted
|
[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(
|
public record AuthMethodResponse(
|
||||||
Snowflake Id,
|
Snowflake Id,
|
||||||
[property: JsonConverter(typeof(ScreamingSnakeCaseEnumConverter))] AuthType Type,
|
[property: JsonConverter(typeof(ScreamingSnakeCaseEnumConverter))] AuthType Type,
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
// 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,7 +19,6 @@ using Foxnouns.Backend.Jobs;
|
||||||
using Foxnouns.Backend.Middleware;
|
using Foxnouns.Backend.Middleware;
|
||||||
using Foxnouns.Backend.Services;
|
using Foxnouns.Backend.Services;
|
||||||
using Foxnouns.Backend.Services.Auth;
|
using Foxnouns.Backend.Services.Auth;
|
||||||
using Foxnouns.Backend.Services.V1;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Minio;
|
using Minio;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
|
@ -128,9 +127,7 @@ public static class WebApplicationExtensions
|
||||||
.AddTransient<MemberAvatarUpdateInvocable>()
|
.AddTransient<MemberAvatarUpdateInvocable>()
|
||||||
.AddTransient<UserAvatarUpdateInvocable>()
|
.AddTransient<UserAvatarUpdateInvocable>()
|
||||||
.AddTransient<CreateFlagInvocable>()
|
.AddTransient<CreateFlagInvocable>()
|
||||||
.AddTransient<CreateDataExportInvocable>()
|
.AddTransient<CreateDataExportInvocable>();
|
||||||
// Legacy services
|
|
||||||
.AddScoped<UsersV1Service>();
|
|
||||||
|
|
||||||
if (!config.Logging.EnableMetrics)
|
if (!config.Logging.EnableMetrics)
|
||||||
services.AddHostedService<BackgroundMetricsCollectionService>();
|
services.AddHostedService<BackgroundMetricsCollectionService>();
|
||||||
|
|
|
@ -44,7 +44,6 @@
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.6"/>
|
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.6"/>
|
||||||
<PackageReference Include="System.Text.Json" Version="9.0.0"/>
|
<PackageReference Include="System.Text.Json" Version="9.0.0"/>
|
||||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1"/>
|
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1"/>
|
||||||
<PackageReference Include="Yort.Xid.Net" Version="2.0.1"/>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="SetSourceRevisionId" BeforeTargets="InitializeSourceControlInformation">
|
<Target Name="SetSourceRevisionId" BeforeTargets="InitializeSourceControlInformation">
|
||||||
|
|
|
@ -20,7 +20,6 @@ using Foxnouns.Backend.Utils;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using XidNet;
|
|
||||||
|
|
||||||
namespace Foxnouns.Backend.Services.Auth;
|
namespace Foxnouns.Backend.Services.Auth;
|
||||||
|
|
||||||
|
@ -71,7 +70,6 @@ public class AuthService(
|
||||||
},
|
},
|
||||||
LastActive = clock.GetCurrentInstant(),
|
LastActive = clock.GetCurrentInstant(),
|
||||||
Sid = null!,
|
Sid = null!,
|
||||||
LegacyId = Xid.NewXid().ToString(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
db.Add(user);
|
db.Add(user);
|
||||||
|
@ -118,7 +116,6 @@ public class AuthService(
|
||||||
},
|
},
|
||||||
LastActive = clock.GetCurrentInstant(),
|
LastActive = clock.GetCurrentInstant(),
|
||||||
Sid = null!,
|
Sid = null!,
|
||||||
LegacyId = Xid.NewXid().ToString(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
db.Add(user);
|
db.Add(user);
|
||||||
|
|
|
@ -103,8 +103,7 @@ public class UserRendererService(
|
||||||
user.Names,
|
user.Names,
|
||||||
user.Pronouns,
|
user.Pronouns,
|
||||||
user.Fields,
|
user.Fields,
|
||||||
user.CustomPreferences.Select(x => (x.Key, RenderCustomPreference(x.Value)))
|
user.CustomPreferences,
|
||||||
.ToDictionary(),
|
|
||||||
flags.Select(f => RenderPrideFlag(f.PrideFlag)),
|
flags.Select(f => RenderPrideFlag(f.PrideFlag)),
|
||||||
utcOffset,
|
utcOffset,
|
||||||
user.Role,
|
user.Role,
|
||||||
|
@ -131,14 +130,6 @@ public class UserRendererService(
|
||||||
: a.RemoteUsername
|
: 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) =>
|
public PartialUser RenderPartialUser(User user) =>
|
||||||
new(
|
new(
|
||||||
user.Id,
|
user.Id,
|
||||||
|
|
|
@ -1,92 +0,0 @@
|
||||||
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()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
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,12 +293,6 @@
|
||||||
"System.Runtime": "4.3.1"
|
"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": {
|
"BouncyCastle.Cryptography": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "2.5.0",
|
"resolved": "2.5.0",
|
||||||
|
|
|
@ -39,7 +39,6 @@ public class UserMigrator(
|
||||||
_user = new User
|
_user = new User
|
||||||
{
|
{
|
||||||
Id = goUser.SnowflakeId,
|
Id = goUser.SnowflakeId,
|
||||||
LegacyId = goUser.Id,
|
|
||||||
Username = goUser.Username,
|
Username = goUser.Username,
|
||||||
DisplayName = goUser.DisplayName,
|
DisplayName = goUser.DisplayName,
|
||||||
Bio = goUser.Bio,
|
Bio = goUser.Bio,
|
||||||
|
@ -140,7 +139,6 @@ public class UserMigrator(
|
||||||
new PrideFlag
|
new PrideFlag
|
||||||
{
|
{
|
||||||
Id = flag.SnowflakeId,
|
Id = flag.SnowflakeId,
|
||||||
LegacyId = flag.Id,
|
|
||||||
UserId = _user!.Id,
|
UserId = _user!.Id,
|
||||||
Hash = flag.Hash,
|
Hash = flag.Hash,
|
||||||
Name = flag.Name,
|
Name = flag.Name,
|
||||||
|
@ -192,7 +190,6 @@ public class UserMigrator(
|
||||||
UserId = _user!.Id,
|
UserId = _user!.Id,
|
||||||
Name = goMember.Name,
|
Name = goMember.Name,
|
||||||
Sid = goMember.Sid,
|
Sid = goMember.Sid,
|
||||||
LegacyId = goMember.Id,
|
|
||||||
DisplayName = goMember.DisplayName,
|
DisplayName = goMember.DisplayName,
|
||||||
Bio = goMember.Bio,
|
Bio = goMember.Bio,
|
||||||
Avatar = goMember.Avatar,
|
Avatar = goMember.Avatar,
|
||||||
|
@ -238,7 +235,6 @@ public class UserMigrator(
|
||||||
"small" => PreferenceSize.Small,
|
"small" => PreferenceSize.Small,
|
||||||
_ => PreferenceSize.Normal,
|
_ => PreferenceSize.Normal,
|
||||||
},
|
},
|
||||||
LegacyId = new Guid(id),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,11 +64,3 @@
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.big-footer {
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
background-color: bootstrap.shade-color(bootstrap.$dark, 20%);
|
|
||||||
}
|
|
||||||
|
|
||||||
background-color: bootstrap.shade-color(bootstrap.$light, 5%);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import type { Meta } from "$api/models";
|
|
||||||
import Git from "svelte-bootstrap-icons/lib/Git.svelte";
|
|
||||||
import Reception4 from "svelte-bootstrap-icons/lib/Reception4.svelte";
|
|
||||||
import Newspaper from "svelte-bootstrap-icons/lib/Newspaper.svelte";
|
|
||||||
import CardText from "svelte-bootstrap-icons/lib/CardText.svelte";
|
|
||||||
import Shield from "svelte-bootstrap-icons/lib/Shield.svelte";
|
|
||||||
import Envelope from "svelte-bootstrap-icons/lib/Envelope.svelte";
|
|
||||||
import CashCoin from "svelte-bootstrap-icons/lib/CashCoin.svelte";
|
|
||||||
import Logo from "./Logo.svelte";
|
|
||||||
|
|
||||||
type Props = { meta: Meta };
|
|
||||||
let { meta }: Props = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<footer class="big-footer mt-3 pt-3 pb-1 px-5">
|
|
||||||
<div class="d-flex flex-column flex-md-row mb-2">
|
|
||||||
<div class="align-start flex-grow-1">
|
|
||||||
<Logo />
|
|
||||||
<ul class="mt-2 list-unstyled">
|
|
||||||
<li><strong>Version</strong> {meta.version}</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="align-end">
|
|
||||||
<ul class="list-unstyled">
|
|
||||||
<li>{meta.users.total} <strong>users</strong></li>
|
|
||||||
<li>{meta.members} <strong>members</strong></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul class="list-inline">
|
|
||||||
<a
|
|
||||||
class="list-inline-item link-underline link-underline-opacity-0"
|
|
||||||
target="_blank"
|
|
||||||
href={meta.repository}
|
|
||||||
>
|
|
||||||
<li class="list-inline-item">
|
|
||||||
<Git />
|
|
||||||
Source code
|
|
||||||
</li>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="list-inline-item link-underline link-underline-opacity-0"
|
|
||||||
target="_blank"
|
|
||||||
href="https://status.pronouns.cc"
|
|
||||||
>
|
|
||||||
<li class="list-inline-item">
|
|
||||||
<Reception4 />
|
|
||||||
Status
|
|
||||||
</li>
|
|
||||||
</a>
|
|
||||||
<a class="list-inline-item link-underline link-underline-opacity-0" href="/page/about">
|
|
||||||
<li class="list-inline-item">
|
|
||||||
<Envelope />
|
|
||||||
About and contact
|
|
||||||
</li>
|
|
||||||
</a>
|
|
||||||
<a class="list-inline-item link-underline link-underline-opacity-0" href="/page/tos">
|
|
||||||
<li class="list-inline-item">
|
|
||||||
<CardText />
|
|
||||||
Terms of service
|
|
||||||
</li>
|
|
||||||
</a>
|
|
||||||
<a class="list-inline-item link-underline link-underline-opacity-0" href="/page/privacy">
|
|
||||||
<li class="list-inline-item">
|
|
||||||
<Shield />
|
|
||||||
Privacy policy
|
|
||||||
</li>
|
|
||||||
</a>
|
|
||||||
<a class="list-inline-item link-underline link-underline-opacity-0" href="/page/changelog">
|
|
||||||
<li class="list-inline-item">
|
|
||||||
<Newspaper />
|
|
||||||
Changelog
|
|
||||||
</li>
|
|
||||||
</a>
|
|
||||||
<a class="list-inline-item link-underline link-underline-opacity-0" href="/page/donate">
|
|
||||||
<li class="list-inline-item">
|
|
||||||
<CashCoin />
|
|
||||||
Donate
|
|
||||||
</li>
|
|
||||||
</a>
|
|
||||||
</ul>
|
|
||||||
</footer>
|
|
|
@ -3,16 +3,11 @@
|
||||||
import "../app.scss";
|
import "../app.scss";
|
||||||
import type { LayoutData } from "./$types";
|
import type { LayoutData } from "./$types";
|
||||||
import Navbar from "$components/Navbar.svelte";
|
import Navbar from "$components/Navbar.svelte";
|
||||||
import Footer from "$components/Footer.svelte";
|
|
||||||
|
|
||||||
type Props = { children: Snippet; data: LayoutData };
|
type Props = { children: Snippet; data: LayoutData };
|
||||||
let { children, data }: Props = $props();
|
let { children, data }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="d-flex flex-column min-vh-100">
|
<Navbar user={data.meUser} meta={data.meta} />
|
||||||
<div class="flex-grow-1">
|
|
||||||
<Navbar user={data.meUser} meta={data.meta} />
|
{@render children?.()}
|
||||||
{@render children?.()}
|
|
||||||
</div>
|
|
||||||
<Footer meta={data.meta} />
|
|
||||||
</div>
|
|
||||||
|
|
Loading…
Reference in a new issue