diff --git a/Foxnouns.Backend/Controllers/FlagsController.cs b/Foxnouns.Backend/Controllers/FlagsController.cs deleted file mode 100644 index 7bf20e5..0000000 --- a/Foxnouns.Backend/Controllers/FlagsController.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Coravel.Queuing.Interfaces; -using Foxnouns.Backend.Database; -using Foxnouns.Backend.Database.Models; -using Foxnouns.Backend.Jobs; -using Foxnouns.Backend.Middleware; -using Foxnouns.Backend.Services; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; - -namespace Foxnouns.Backend.Controllers; - -[Route("/api/v2/users/@me/flags")] -public class FlagsController( - DatabaseContext db, - UserRendererService userRenderer, - ISnowflakeGenerator snowflakeGenerator, - IQueue queue) : ApiControllerBase -{ - [HttpGet] - [Authorize("identify")] - [ProducesResponseType>(statusCode: StatusCodes.Status200OK)] - public async Task GetFlagsAsync(CancellationToken ct = default) - { - var flags = await db.PrideFlags.Where(f => f.UserId == CurrentUser!.Id).ToListAsync(ct); - - return Ok(flags.Select(ToResponse)); - } - - [HttpPost] - [Authorize("user.update")] - [ProducesResponseType(statusCode: StatusCodes.Status202Accepted)] - public IActionResult CreateFlag([FromBody] CreateFlagRequest req) - { - var id = snowflakeGenerator.GenerateSnowflake(); - - queue.QueueInvocableWithPayload( - new CreateFlagPayload(id, CurrentUser!.Id, req.Name, req.Image, req.Description)); - - return Accepted(new CreateFlagResponse(id, req.Name, req.Description)); - } - - public record CreateFlagRequest(string Name, string Image, string? Description); - - public record CreateFlagResponse(Snowflake Id, string Name, string? Description); - - private PrideFlagResponse ToResponse(PrideFlag flag) => - new(flag.Id, userRenderer.ImageUrlFor(flag), flag.Name, flag.Description); - - private record PrideFlagResponse( - Snowflake Id, - string ImageUrl, - string Name, - string? Description); -} \ No newline at end of file diff --git a/Foxnouns.Backend/Database/DatabaseContext.cs b/Foxnouns.Backend/Database/DatabaseContext.cs index 81866fe..e907abd 100644 --- a/Foxnouns.Backend/Database/DatabaseContext.cs +++ b/Foxnouns.Backend/Database/DatabaseContext.cs @@ -21,10 +21,6 @@ public class DatabaseContext : DbContext public DbSet Tokens { get; set; } public DbSet Applications { get; set; } public DbSet TemporaryKeys { get; set; } - - public DbSet PrideFlags { get; set; } - public DbSet UserFlags { get; set; } - public DbSet MemberFlags { get; set; } public DatabaseContext(Config config, ILoggerFactory? loggerFactory) { @@ -81,9 +77,6 @@ public class DatabaseContext : DbContext modelBuilder.Entity().Property(m => m.Names).HasColumnType("jsonb"); modelBuilder.Entity().Property(m => m.Pronouns).HasColumnType("jsonb"); - modelBuilder.Entity().Navigation(f => f.PrideFlag).AutoInclude(); - modelBuilder.Entity().Navigation(f => f.PrideFlag).AutoInclude(); - modelBuilder.HasDbFunction(typeof(DatabaseContext).GetMethod(nameof(FindFreeUserSid))!) .HasName("find_free_user_sid"); diff --git a/Foxnouns.Backend/Database/Migrations/20240926180037_AddFlags.cs b/Foxnouns.Backend/Database/Migrations/20240926180037_AddFlags.cs deleted file mode 100644 index ae59b0b..0000000 --- a/Foxnouns.Backend/Database/Migrations/20240926180037_AddFlags.cs +++ /dev/null @@ -1,129 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Foxnouns.Backend.Database.Migrations -{ - /// - [DbContext(typeof(DatabaseContext))] - [Migration("20240926180037_AddFlags")] - public partial class AddFlags : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "pride_flags", - columns: table => new - { - id = table.Column(type: "bigint", nullable: false), - user_id = table.Column(type: "bigint", nullable: false), - hash = table.Column(type: "text", nullable: false), - name = table.Column(type: "text", nullable: false), - description = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_pride_flags", x => x.id); - table.ForeignKey( - name: "fk_pride_flags_users_user_id", - column: x => x.user_id, - principalTable: "users", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "member_flags", - columns: table => new - { - id = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - member_id = table.Column(type: "bigint", nullable: false), - pride_flag_id = table.Column(type: "bigint", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_member_flags", x => x.id); - table.ForeignKey( - name: "fk_member_flags_members_member_id", - column: x => x.member_id, - principalTable: "members", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_member_flags_pride_flags_pride_flag_id", - column: x => x.pride_flag_id, - principalTable: "pride_flags", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "user_flags", - columns: table => new - { - id = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - user_id = table.Column(type: "bigint", nullable: false), - pride_flag_id = table.Column(type: "bigint", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_user_flags", x => x.id); - table.ForeignKey( - name: "fk_user_flags_pride_flags_pride_flag_id", - column: x => x.pride_flag_id, - principalTable: "pride_flags", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_user_flags_users_user_id", - column: x => x.user_id, - principalTable: "users", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "ix_member_flags_member_id", - table: "member_flags", - column: "member_id"); - - migrationBuilder.CreateIndex( - name: "ix_member_flags_pride_flag_id", - table: "member_flags", - column: "pride_flag_id"); - - migrationBuilder.CreateIndex( - name: "ix_pride_flags_user_id", - table: "pride_flags", - column: "user_id"); - - migrationBuilder.CreateIndex( - name: "ix_user_flags_pride_flag_id", - table: "user_flags", - column: "pride_flag_id"); - - migrationBuilder.CreateIndex( - name: "ix_user_flags_user_id", - table: "user_flags", - column: "user_id"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "member_flags"); - - migrationBuilder.DropTable( - name: "user_flags"); - - migrationBuilder.DropTable( - name: "pride_flags"); - } - } -} diff --git a/Foxnouns.Backend/Database/Migrations/DatabaseContextModelSnapshot.cs b/Foxnouns.Backend/Database/Migrations/DatabaseContextModelSnapshot.cs index e1e05c2..dd457cf 100644 --- a/Foxnouns.Backend/Database/Migrations/DatabaseContextModelSnapshot.cs +++ b/Foxnouns.Backend/Database/Migrations/DatabaseContextModelSnapshot.cs @@ -204,68 +204,6 @@ namespace Foxnouns.Backend.Database.Migrations b.ToTable("members", (string)null); }); - modelBuilder.Entity("Foxnouns.Backend.Database.Models.MemberFlag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("MemberId") - .HasColumnType("bigint") - .HasColumnName("member_id"); - - b.Property("PrideFlagId") - .HasColumnType("bigint") - .HasColumnName("pride_flag_id"); - - b.HasKey("Id") - .HasName("pk_member_flags"); - - b.HasIndex("MemberId") - .HasDatabaseName("ix_member_flags_member_id"); - - b.HasIndex("PrideFlagId") - .HasDatabaseName("ix_member_flags_pride_flag_id"); - - b.ToTable("member_flags", (string)null); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.PrideFlag", b => - { - b.Property("Id") - .HasColumnType("bigint") - .HasColumnName("id"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Hash") - .IsRequired() - .HasColumnType("text") - .HasColumnName("hash"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text") - .HasColumnName("name"); - - b.Property("UserId") - .HasColumnType("bigint") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_pride_flags"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_pride_flags_user_id"); - - b.ToTable("pride_flags", (string)null); - }); - modelBuilder.Entity("Foxnouns.Backend.Database.Models.TemporaryKey", b => { b.Property("Id") @@ -453,35 +391,6 @@ namespace Foxnouns.Backend.Database.Migrations b.ToTable("users", (string)null); }); - modelBuilder.Entity("Foxnouns.Backend.Database.Models.UserFlag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PrideFlagId") - .HasColumnType("bigint") - .HasColumnName("pride_flag_id"); - - b.Property("UserId") - .HasColumnType("bigint") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_flags"); - - b.HasIndex("PrideFlagId") - .HasDatabaseName("ix_user_flags_pride_flag_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_flags_user_id"); - - b.ToTable("user_flags", (string)null); - }); - modelBuilder.Entity("Foxnouns.Backend.Database.Models.AuthMethod", b => { b.HasOne("Foxnouns.Backend.Database.Models.FediverseApplication", "FediverseApplication") @@ -513,35 +422,6 @@ namespace Foxnouns.Backend.Database.Migrations b.Navigation("User"); }); - modelBuilder.Entity("Foxnouns.Backend.Database.Models.MemberFlag", b => - { - b.HasOne("Foxnouns.Backend.Database.Models.Member", null) - .WithMany("ProfileFlags") - .HasForeignKey("MemberId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_member_flags_members_member_id"); - - b.HasOne("Foxnouns.Backend.Database.Models.PrideFlag", "PrideFlag") - .WithMany() - .HasForeignKey("PrideFlagId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_member_flags_pride_flags_pride_flag_id"); - - b.Navigation("PrideFlag"); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.PrideFlag", b => - { - b.HasOne("Foxnouns.Backend.Database.Models.User", null) - .WithMany("Flags") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_pride_flags_users_user_id"); - }); - modelBuilder.Entity("Foxnouns.Backend.Database.Models.Token", b => { b.HasOne("Foxnouns.Backend.Database.Models.Application", "Application") @@ -563,39 +443,11 @@ namespace Foxnouns.Backend.Database.Migrations b.Navigation("User"); }); - modelBuilder.Entity("Foxnouns.Backend.Database.Models.UserFlag", b => - { - b.HasOne("Foxnouns.Backend.Database.Models.PrideFlag", "PrideFlag") - .WithMany() - .HasForeignKey("PrideFlagId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_flags_pride_flags_pride_flag_id"); - - b.HasOne("Foxnouns.Backend.Database.Models.User", null) - .WithMany("ProfileFlags") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_flags_users_user_id"); - - b.Navigation("PrideFlag"); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.Member", b => - { - b.Navigation("ProfileFlags"); - }); - modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b => { b.Navigation("AuthMethods"); - b.Navigation("Flags"); - b.Navigation("Members"); - - b.Navigation("ProfileFlags"); }); #pragma warning restore 612, 618 } diff --git a/Foxnouns.Backend/Database/Models/Member.cs b/Foxnouns.Backend/Database/Models/Member.cs index ceaf84a..d20d374 100644 --- a/Foxnouns.Backend/Database/Models/Member.cs +++ b/Foxnouns.Backend/Database/Models/Member.cs @@ -14,8 +14,6 @@ public class Member : BaseModel public List Pronouns { get; set; } = []; public List Fields { get; set; } = []; - public List ProfileFlags { get; set; } = []; - public Snowflake UserId { get; init; } public User User { get; init; } = null!; } \ No newline at end of file diff --git a/Foxnouns.Backend/Database/Models/PrideFlag.cs b/Foxnouns.Backend/Database/Models/PrideFlag.cs deleted file mode 100644 index b7f91cf..0000000 --- a/Foxnouns.Backend/Database/Models/PrideFlag.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Foxnouns.Backend.Database.Models; - -public class PrideFlag : BaseModel -{ - public required Snowflake UserId { get; init; } - public required string Hash { get; init; } - public required string Name { get; set; } - public string? Description { get; set; } -} - -public class UserFlag -{ - public long Id { get; init; } - public required Snowflake UserId { get; init; } - public required Snowflake PrideFlagId { get; init; } - public PrideFlag PrideFlag { get; init; } = null!; -} - -public class MemberFlag -{ - public long Id { get; init; } - public required Snowflake MemberId { get; init; } - public required Snowflake PrideFlagId { get; init; } - public PrideFlag PrideFlag { get; init; } = null!; -} \ No newline at end of file diff --git a/Foxnouns.Backend/Database/Models/User.cs b/Foxnouns.Backend/Database/Models/User.cs index 2328fbe..112e560 100644 --- a/Foxnouns.Backend/Database/Models/User.cs +++ b/Foxnouns.Backend/Database/Models/User.cs @@ -21,9 +21,6 @@ public class User : BaseModel public List Fields { get; set; } = []; public Dictionary CustomPreferences { get; set; } = []; - public List Flags { get; set; } = []; - public List ProfileFlags { get; set; } = []; - public UserRole Role { get; set; } = UserRole.User; public string? Password { get; set; } // Password may be null if the user doesn't authenticate with an email address diff --git a/Foxnouns.Backend/Extensions/AvatarObjectExtensions.cs b/Foxnouns.Backend/Extensions/AvatarObjectExtensions.cs index c3c3c04..7c39aa4 100644 --- a/Foxnouns.Backend/Extensions/AvatarObjectExtensions.cs +++ b/Foxnouns.Backend/Extensions/AvatarObjectExtensions.cs @@ -23,7 +23,7 @@ public static class AvatarObjectExtensions CancellationToken ct = default) => await objectStorageService.RemoveObjectAsync(UserAvatarUpdateInvocable.Path(id, hash), ct); - public static async Task ConvertBase64UriToImage(this string uri, int size, bool crop) + public static async Task ConvertBase64UriToAvatar(this string uri) { if (!uri.StartsWith("data:image/")) throw new ArgumentException("Not a data URI", nameof(uri)); @@ -40,12 +40,7 @@ public static class AvatarObjectExtensions var image = Image.Load(rawImage); var processor = new ResizeProcessor( - new ResizeOptions - { - Size = new Size(size), - Mode = crop ? ResizeMode.Crop : ResizeMode.Max, - Position = AnchorPositionMode.Center - }, + new ResizeOptions { Size = new Size(512), Mode = ResizeMode.Crop, Position = AnchorPositionMode.Center }, image.Size ); diff --git a/Foxnouns.Backend/Extensions/WebApplicationExtensions.cs b/Foxnouns.Backend/Extensions/WebApplicationExtensions.cs index e249fd7..a5b2af6 100644 --- a/Foxnouns.Backend/Extensions/WebApplicationExtensions.cs +++ b/Foxnouns.Backend/Extensions/WebApplicationExtensions.cs @@ -99,8 +99,7 @@ public static class WebApplicationExtensions .AddHostedService() // Transient jobs .AddTransient() - .AddTransient() - .AddTransient(); + .AddTransient(); if (!config.Logging.EnableMetrics) services.AddHostedService(); diff --git a/Foxnouns.Backend/Jobs/CreateFlagInvocable.cs b/Foxnouns.Backend/Jobs/CreateFlagInvocable.cs deleted file mode 100644 index 6f69794..0000000 --- a/Foxnouns.Backend/Jobs/CreateFlagInvocable.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Security.Cryptography; -using Coravel.Invocable; -using Foxnouns.Backend.Database; -using Foxnouns.Backend.Database.Models; -using Foxnouns.Backend.Extensions; -using Foxnouns.Backend.Services; - -namespace Foxnouns.Backend.Jobs; - -public class CreateFlagInvocable(DatabaseContext db, ObjectStorageService objectStorageService, ILogger logger) - : IInvocable, IInvocableWithPayload -{ - private readonly ILogger _logger = logger.ForContext(); - public required CreateFlagPayload Payload { get; set; } - - public async Task Invoke() - { - _logger.Information("Creating flag {FlagId} for user {UserId} with image data length {DataLength}", Payload.Id, - Payload.UserId, Payload.ImageData.Length); - - try - { - var image = await Payload.ImageData.ConvertBase64UriToImage(size: 256, crop: false); - image.Seek(0, SeekOrigin.Begin); - var hash = Convert.ToHexString(await SHA256.HashDataAsync(image)).ToLower(); - image.Seek(0, SeekOrigin.Begin); - - await objectStorageService.PutObjectAsync(Path(hash), image, "image/webp"); - - var flag = new PrideFlag - { - Id = Payload.Id, - UserId = Payload.UserId, - Hash = hash, - Name = Payload.Name, - Description = Payload.Description - }; - db.Add(flag); - - await db.SaveChangesAsync(); - - _logger.Information("Uploaded flag {FlagId} with hash {Hash}", flag.Id, flag.Hash); - } - catch (ArgumentException ae) - { - _logger.Warning("Invalid data URI for flag {FlagId}: {Reason}", Payload.Id, ae.Message); - } - - throw new NotImplementedException(); - } - - private static string Path(string hash) => $"flags/{hash}.webp"; -} \ No newline at end of file diff --git a/Foxnouns.Backend/Jobs/MemberAvatarUpdateInvocable.cs b/Foxnouns.Backend/Jobs/MemberAvatarUpdateInvocable.cs index 7ed801a..3beff48 100644 --- a/Foxnouns.Backend/Jobs/MemberAvatarUpdateInvocable.cs +++ b/Foxnouns.Backend/Jobs/MemberAvatarUpdateInvocable.cs @@ -31,7 +31,7 @@ public class MemberAvatarUpdateInvocable(DatabaseContext db, ObjectStorageServic try { - var image = await newAvatar.ConvertBase64UriToImage(size: 512, crop: true); + var image = await newAvatar.ConvertBase64UriToAvatar(); var hash = Convert.ToHexString(await SHA256.HashDataAsync(image)).ToLower(); image.Seek(0, SeekOrigin.Begin); var prevHash = member.Avatar; diff --git a/Foxnouns.Backend/Jobs/Payloads.cs b/Foxnouns.Backend/Jobs/Payloads.cs index 672f6d6..f28254a 100644 --- a/Foxnouns.Backend/Jobs/Payloads.cs +++ b/Foxnouns.Backend/Jobs/Payloads.cs @@ -2,6 +2,4 @@ using Foxnouns.Backend.Database; namespace Foxnouns.Backend.Jobs; -public record AvatarUpdatePayload(Snowflake Id, string? NewAvatar); - -public record CreateFlagPayload(Snowflake Id, Snowflake UserId, string Name, string ImageData, string? Description); \ No newline at end of file +public record AvatarUpdatePayload(Snowflake Id, string? NewAvatar); \ No newline at end of file diff --git a/Foxnouns.Backend/Jobs/UserAvatarUpdateInvocable.cs b/Foxnouns.Backend/Jobs/UserAvatarUpdateInvocable.cs index 44dd312..d1abd42 100644 --- a/Foxnouns.Backend/Jobs/UserAvatarUpdateInvocable.cs +++ b/Foxnouns.Backend/Jobs/UserAvatarUpdateInvocable.cs @@ -31,7 +31,7 @@ public class UserAvatarUpdateInvocable(DatabaseContext db, ObjectStorageService try { - var image = await newAvatar.ConvertBase64UriToImage(size: 512, crop: true); + var image = await newAvatar.ConvertBase64UriToAvatar(); var hash = Convert.ToHexString(await SHA256.HashDataAsync(image)).ToLower(); image.Seek(0, SeekOrigin.Begin); var prevHash = user.Avatar; diff --git a/Foxnouns.Backend/Services/MemberRendererService.cs b/Foxnouns.Backend/Services/MemberRendererService.cs index 11416f0..625e8b8 100644 --- a/Foxnouns.Backend/Services/MemberRendererService.cs +++ b/Foxnouns.Backend/Services/MemberRendererService.cs @@ -47,8 +47,6 @@ public class MemberRendererService(DatabaseContext db, Config config) private string? AvatarUrlFor(User user) => user.Avatar != null ? $"{config.MediaBaseUrl}/users/{user.Id}/avatars/{user.Avatar}.webp" : null; - private string ImageUrlFor(PrideFlag flag) => $"{config.MediaBaseUrl}/flags/{flag.Hash}.webp"; - public record PartialMember( Snowflake Id, string Sid, diff --git a/Foxnouns.Backend/Services/UserRendererService.cs b/Foxnouns.Backend/Services/UserRendererService.cs index 688214d..0f8fe2e 100644 --- a/Foxnouns.Backend/Services/UserRendererService.cs +++ b/Foxnouns.Backend/Services/UserRendererService.cs @@ -58,8 +58,6 @@ public class UserRendererService(DatabaseContext db, MemberRendererService membe private string? AvatarUrlFor(User user) => user.Avatar != null ? $"{config.MediaBaseUrl}/users/{user.Id}/avatars/{user.Avatar}.webp" : null; - - public string ImageUrlFor(PrideFlag flag) => $"{config.MediaBaseUrl}/flags/{flag.Hash}.webp"; public record UserResponse( Snowflake Id,