Compare commits
No commits in common. "14e6e35cb7aa4b7ebc1754582ee1036d51438a5b" and "39b091758521ef16c236e81d3dfbf9be2f6f5731" have entirely different histories.
14e6e35cb7
...
39b0917585
15 changed files with 6 additions and 439 deletions
|
@ -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<IEnumerable<PrideFlagResponse>>(statusCode: StatusCodes.Status200OK)]
|
|
||||||
public async Task<IActionResult> 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<PrideFlagResponse>(statusCode: StatusCodes.Status202Accepted)]
|
|
||||||
public IActionResult CreateFlag([FromBody] CreateFlagRequest req)
|
|
||||||
{
|
|
||||||
var id = snowflakeGenerator.GenerateSnowflake();
|
|
||||||
|
|
||||||
queue.QueueInvocableWithPayload<CreateFlagInvocable, CreateFlagPayload>(
|
|
||||||
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);
|
|
||||||
}
|
|
|
@ -21,10 +21,6 @@ public class DatabaseContext : DbContext
|
||||||
public DbSet<Token> Tokens { get; set; }
|
public DbSet<Token> Tokens { get; set; }
|
||||||
public DbSet<Application> Applications { get; set; }
|
public DbSet<Application> Applications { get; set; }
|
||||||
public DbSet<TemporaryKey> TemporaryKeys { get; set; }
|
public DbSet<TemporaryKey> TemporaryKeys { get; set; }
|
||||||
|
|
||||||
public DbSet<PrideFlag> PrideFlags { get; set; }
|
|
||||||
public DbSet<UserFlag> UserFlags { get; set; }
|
|
||||||
public DbSet<MemberFlag> MemberFlags { get; set; }
|
|
||||||
|
|
||||||
public DatabaseContext(Config config, ILoggerFactory? loggerFactory)
|
public DatabaseContext(Config config, ILoggerFactory? loggerFactory)
|
||||||
{
|
{
|
||||||
|
@ -81,9 +77,6 @@ public class DatabaseContext : DbContext
|
||||||
modelBuilder.Entity<Member>().Property(m => m.Names).HasColumnType("jsonb");
|
modelBuilder.Entity<Member>().Property(m => m.Names).HasColumnType("jsonb");
|
||||||
modelBuilder.Entity<Member>().Property(m => m.Pronouns).HasColumnType("jsonb");
|
modelBuilder.Entity<Member>().Property(m => m.Pronouns).HasColumnType("jsonb");
|
||||||
|
|
||||||
modelBuilder.Entity<UserFlag>().Navigation(f => f.PrideFlag).AutoInclude();
|
|
||||||
modelBuilder.Entity<MemberFlag>().Navigation(f => f.PrideFlag).AutoInclude();
|
|
||||||
|
|
||||||
modelBuilder.HasDbFunction(typeof(DatabaseContext).GetMethod(nameof(FindFreeUserSid))!)
|
modelBuilder.HasDbFunction(typeof(DatabaseContext).GetMethod(nameof(FindFreeUserSid))!)
|
||||||
.HasName("find_free_user_sid");
|
.HasName("find_free_user_sid");
|
||||||
|
|
||||||
|
|
|
@ -1,129 +0,0 @@
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace Foxnouns.Backend.Database.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
[DbContext(typeof(DatabaseContext))]
|
|
||||||
[Migration("20240926180037_AddFlags")]
|
|
||||||
public partial class AddFlags : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "pride_flags",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
id = table.Column<long>(type: "bigint", nullable: false),
|
|
||||||
user_id = table.Column<long>(type: "bigint", nullable: false),
|
|
||||||
hash = table.Column<string>(type: "text", nullable: false),
|
|
||||||
name = table.Column<string>(type: "text", nullable: false),
|
|
||||||
description = table.Column<string>(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<long>(type: "bigint", nullable: false)
|
|
||||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
|
||||||
member_id = table.Column<long>(type: "bigint", nullable: false),
|
|
||||||
pride_flag_id = table.Column<long>(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<long>(type: "bigint", nullable: false)
|
|
||||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
|
||||||
user_id = table.Column<long>(type: "bigint", nullable: false),
|
|
||||||
pride_flag_id = table.Column<long>(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");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "member_flags");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "user_flags");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "pride_flags");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -204,68 +204,6 @@ namespace Foxnouns.Backend.Database.Migrations
|
||||||
b.ToTable("members", (string)null);
|
b.ToTable("members", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Foxnouns.Backend.Database.Models.MemberFlag", b =>
|
|
||||||
{
|
|
||||||
b.Property<long>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("bigint")
|
|
||||||
.HasColumnName("id");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
|
||||||
|
|
||||||
b.Property<long>("MemberId")
|
|
||||||
.HasColumnType("bigint")
|
|
||||||
.HasColumnName("member_id");
|
|
||||||
|
|
||||||
b.Property<long>("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<long>("Id")
|
|
||||||
.HasColumnType("bigint")
|
|
||||||
.HasColumnName("id");
|
|
||||||
|
|
||||||
b.Property<string>("Description")
|
|
||||||
.HasColumnType("text")
|
|
||||||
.HasColumnName("description");
|
|
||||||
|
|
||||||
b.Property<string>("Hash")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text")
|
|
||||||
.HasColumnName("hash");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("text")
|
|
||||||
.HasColumnName("name");
|
|
||||||
|
|
||||||
b.Property<long>("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 =>
|
modelBuilder.Entity("Foxnouns.Backend.Database.Models.TemporaryKey", b =>
|
||||||
{
|
{
|
||||||
b.Property<long>("Id")
|
b.Property<long>("Id")
|
||||||
|
@ -453,35 +391,6 @@ namespace Foxnouns.Backend.Database.Migrations
|
||||||
b.ToTable("users", (string)null);
|
b.ToTable("users", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Foxnouns.Backend.Database.Models.UserFlag", b =>
|
|
||||||
{
|
|
||||||
b.Property<long>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("bigint")
|
|
||||||
.HasColumnName("id");
|
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
|
||||||
|
|
||||||
b.Property<long>("PrideFlagId")
|
|
||||||
.HasColumnType("bigint")
|
|
||||||
.HasColumnName("pride_flag_id");
|
|
||||||
|
|
||||||
b.Property<long>("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 =>
|
modelBuilder.Entity("Foxnouns.Backend.Database.Models.AuthMethod", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Foxnouns.Backend.Database.Models.FediverseApplication", "FediverseApplication")
|
b.HasOne("Foxnouns.Backend.Database.Models.FediverseApplication", "FediverseApplication")
|
||||||
|
@ -513,35 +422,6 @@ namespace Foxnouns.Backend.Database.Migrations
|
||||||
b.Navigation("User");
|
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 =>
|
modelBuilder.Entity("Foxnouns.Backend.Database.Models.Token", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Foxnouns.Backend.Database.Models.Application", "Application")
|
b.HasOne("Foxnouns.Backend.Database.Models.Application", "Application")
|
||||||
|
@ -563,39 +443,11 @@ namespace Foxnouns.Backend.Database.Migrations
|
||||||
b.Navigation("User");
|
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 =>
|
modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("AuthMethods");
|
b.Navigation("AuthMethods");
|
||||||
|
|
||||||
b.Navigation("Flags");
|
|
||||||
|
|
||||||
b.Navigation("Members");
|
b.Navigation("Members");
|
||||||
|
|
||||||
b.Navigation("ProfileFlags");
|
|
||||||
});
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,6 @@ public class Member : BaseModel
|
||||||
public List<Pronoun> Pronouns { get; set; } = [];
|
public List<Pronoun> Pronouns { get; set; } = [];
|
||||||
public List<Field> Fields { get; set; } = [];
|
public List<Field> Fields { get; set; } = [];
|
||||||
|
|
||||||
public List<MemberFlag> ProfileFlags { get; set; } = [];
|
|
||||||
|
|
||||||
public Snowflake UserId { get; init; }
|
public Snowflake UserId { get; init; }
|
||||||
public User User { get; init; } = null!;
|
public User User { get; init; } = 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!;
|
|
||||||
}
|
|
|
@ -21,9 +21,6 @@ public class User : BaseModel
|
||||||
public List<Field> Fields { get; set; } = [];
|
public List<Field> Fields { get; set; } = [];
|
||||||
public Dictionary<Snowflake, CustomPreference> CustomPreferences { get; set; } = [];
|
public Dictionary<Snowflake, CustomPreference> CustomPreferences { get; set; } = [];
|
||||||
|
|
||||||
public List<PrideFlag> Flags { get; set; } = [];
|
|
||||||
public List<UserFlag> ProfileFlags { get; set; } = [];
|
|
||||||
|
|
||||||
public UserRole Role { get; set; } = UserRole.User;
|
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
|
public string? Password { get; set; } // Password may be null if the user doesn't authenticate with an email address
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ public static class AvatarObjectExtensions
|
||||||
CancellationToken ct = default) =>
|
CancellationToken ct = default) =>
|
||||||
await objectStorageService.RemoveObjectAsync(UserAvatarUpdateInvocable.Path(id, hash), ct);
|
await objectStorageService.RemoveObjectAsync(UserAvatarUpdateInvocable.Path(id, hash), ct);
|
||||||
|
|
||||||
public static async Task<Stream> ConvertBase64UriToImage(this string uri, int size, bool crop)
|
public static async Task<Stream> ConvertBase64UriToAvatar(this string uri)
|
||||||
{
|
{
|
||||||
if (!uri.StartsWith("data:image/"))
|
if (!uri.StartsWith("data:image/"))
|
||||||
throw new ArgumentException("Not a data URI", nameof(uri));
|
throw new ArgumentException("Not a data URI", nameof(uri));
|
||||||
|
@ -40,12 +40,7 @@ public static class AvatarObjectExtensions
|
||||||
var image = Image.Load(rawImage);
|
var image = Image.Load(rawImage);
|
||||||
|
|
||||||
var processor = new ResizeProcessor(
|
var processor = new ResizeProcessor(
|
||||||
new ResizeOptions
|
new ResizeOptions { Size = new Size(512), Mode = ResizeMode.Crop, Position = AnchorPositionMode.Center },
|
||||||
{
|
|
||||||
Size = new Size(size),
|
|
||||||
Mode = crop ? ResizeMode.Crop : ResizeMode.Max,
|
|
||||||
Position = AnchorPositionMode.Center
|
|
||||||
},
|
|
||||||
image.Size
|
image.Size
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -99,8 +99,7 @@ public static class WebApplicationExtensions
|
||||||
.AddHostedService<PeriodicTasksService>()
|
.AddHostedService<PeriodicTasksService>()
|
||||||
// Transient jobs
|
// Transient jobs
|
||||||
.AddTransient<MemberAvatarUpdateInvocable>()
|
.AddTransient<MemberAvatarUpdateInvocable>()
|
||||||
.AddTransient<UserAvatarUpdateInvocable>()
|
.AddTransient<UserAvatarUpdateInvocable>();
|
||||||
.AddTransient<CreateFlagInvocable>();
|
|
||||||
|
|
||||||
if (!config.Logging.EnableMetrics)
|
if (!config.Logging.EnableMetrics)
|
||||||
services.AddHostedService<BackgroundMetricsCollectionService>();
|
services.AddHostedService<BackgroundMetricsCollectionService>();
|
||||||
|
|
|
@ -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<CreateFlagPayload>
|
|
||||||
{
|
|
||||||
private readonly ILogger _logger = logger.ForContext<CreateFlagInvocable>();
|
|
||||||
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";
|
|
||||||
}
|
|
|
@ -31,7 +31,7 @@ public class MemberAvatarUpdateInvocable(DatabaseContext db, ObjectStorageServic
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var image = await newAvatar.ConvertBase64UriToImage(size: 512, crop: true);
|
var image = await newAvatar.ConvertBase64UriToAvatar();
|
||||||
var hash = Convert.ToHexString(await SHA256.HashDataAsync(image)).ToLower();
|
var hash = Convert.ToHexString(await SHA256.HashDataAsync(image)).ToLower();
|
||||||
image.Seek(0, SeekOrigin.Begin);
|
image.Seek(0, SeekOrigin.Begin);
|
||||||
var prevHash = member.Avatar;
|
var prevHash = member.Avatar;
|
||||||
|
|
|
@ -2,6 +2,4 @@ using Foxnouns.Backend.Database;
|
||||||
|
|
||||||
namespace Foxnouns.Backend.Jobs;
|
namespace Foxnouns.Backend.Jobs;
|
||||||
|
|
||||||
public record AvatarUpdatePayload(Snowflake Id, string? NewAvatar);
|
public record AvatarUpdatePayload(Snowflake Id, string? NewAvatar);
|
||||||
|
|
||||||
public record CreateFlagPayload(Snowflake Id, Snowflake UserId, string Name, string ImageData, string? Description);
|
|
|
@ -31,7 +31,7 @@ public class UserAvatarUpdateInvocable(DatabaseContext db, ObjectStorageService
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var image = await newAvatar.ConvertBase64UriToImage(size: 512, crop: true);
|
var image = await newAvatar.ConvertBase64UriToAvatar();
|
||||||
var hash = Convert.ToHexString(await SHA256.HashDataAsync(image)).ToLower();
|
var hash = Convert.ToHexString(await SHA256.HashDataAsync(image)).ToLower();
|
||||||
image.Seek(0, SeekOrigin.Begin);
|
image.Seek(0, SeekOrigin.Begin);
|
||||||
var prevHash = user.Avatar;
|
var prevHash = user.Avatar;
|
||||||
|
|
|
@ -47,8 +47,6 @@ public class MemberRendererService(DatabaseContext db, Config config)
|
||||||
private string? AvatarUrlFor(User user) =>
|
private string? AvatarUrlFor(User user) =>
|
||||||
user.Avatar != null ? $"{config.MediaBaseUrl}/users/{user.Id}/avatars/{user.Avatar}.webp" : null;
|
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(
|
public record PartialMember(
|
||||||
Snowflake Id,
|
Snowflake Id,
|
||||||
string Sid,
|
string Sid,
|
||||||
|
|
|
@ -58,8 +58,6 @@ public class UserRendererService(DatabaseContext db, MemberRendererService membe
|
||||||
|
|
||||||
private string? AvatarUrlFor(User user) =>
|
private string? AvatarUrlFor(User user) =>
|
||||||
user.Avatar != null ? $"{config.MediaBaseUrl}/users/{user.Id}/avatars/{user.Avatar}.webp" : null;
|
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(
|
public record UserResponse(
|
||||||
Snowflake Id,
|
Snowflake Id,
|
||||||
|
|
Loading…
Reference in a new issue