diff --git a/ENDPOINTS.md b/ENDPOINTS.md deleted file mode 100644 index 4f7fcf5..0000000 --- a/ENDPOINTS.md +++ /dev/null @@ -1,45 +0,0 @@ -# List of API endpoints and scopes - -## Scopes - -- `identify`: `@me` will refer to token user (always granted) -- `user.read_privileged`: can read privileged information such as authentication methods -- `user.update`: can update the user's profile. - **cannot** update anything locked behind `user.read_privileged` -- `member.read`: can view member list if it's hidden and enumerate unlisted members -- `member.create`: can create new members -- `member.update`: can edit and delete members - -## Meta - -- [ ] GET `/meta`: gets stats and server information - -## Users - -- [ ] GET `/users/{userRef}`: views current user. - `identify` required to use `@me` as user reference. - `user.read_privileged` required to view authentication methods. - `member.read` required to view unlisted members. -- [ ] PATCH `/users/@me`: updates current user. `user.update` required. -- [ ] DELETE `/users/@me`: deletes current user. `*` required -- [ ] POST `/users/@me/export`: queues new data export. `*` required -- [ ] GET `/users/@me/export`: gets latest data export. `*` required -- [ ] GET `/users/@me/flags`: get all the user's flags. `identify` required -- [ ] POST `/users/@me/flags`: creates a new flag. `user.update` required -- [ ] PATCH `/users/@me/flags/{id}`: updates an existing flag. `user.update` required -- [ ] DELETE `/users/@me/flags/{id}`: deletes a user flag. `user.update` required -- [ ] POST `/users/@me/reroll`: rerolls a user's short ID. `user.update` required - -## Members - -- [ ] GET `/users/{userRef}/members`: gets list of a user's members. - if the user's member list is hidden, - and it is not the authenticated user (or the token doesn't have the `member.read` scope) - returns an empty array. -- [ ] GET `/users/{userRef}/members/{memberRef}`: gets a single member. - will always return a member if it exists, even if the member is unlisted. -- [ ] POST `/users/@me/members`: creates a new member. `member.create` required -- [ ] PATCH `/users/@me/members/{memberRef}`: edits a member. `member.update` required -- [ ] DELETE `/users/@me/members/{memberRef}`: deletes a member. `member.update` required -- [ ] POST `/users/@me/members/{memberRef}/reroll`: rerolls a member's short ID. `member.update` required. -- \ No newline at end of file diff --git a/Foxnouns.Backend/Config.cs b/Foxnouns.Backend/Config.cs index 62a107f..1cb21c6 100644 --- a/Foxnouns.Backend/Config.cs +++ b/Foxnouns.Backend/Config.cs @@ -10,7 +10,6 @@ public class Config public string MediaBaseUrl { get; set; } = null!; public string Address => $"http://{Host}:{Port}"; - public string? MetricsAddress => Logging.MetricsPort != null ? $"http://{Host}:{Logging.MetricsPort}" : null; public LoggingConfig Logging { get; init; } = new(); public DatabaseConfig Database { get; init; } = new(); @@ -27,8 +26,6 @@ public class Config public string? SentryUrl { get; init; } public bool SentryTracing { get; init; } = false; public double SentryTracesSampleRate { get; init; } = 0.0; - public bool LogQueries { get; init; } = false; - public int? MetricsPort { get; init; } } public class DatabaseConfig diff --git a/Foxnouns.Backend/Controllers/MetaController.cs b/Foxnouns.Backend/Controllers/MetaController.cs index 5dded77..9d2c991 100644 --- a/Foxnouns.Backend/Controllers/MetaController.cs +++ b/Foxnouns.Backend/Controllers/MetaController.cs @@ -1,38 +1,29 @@ using Foxnouns.Backend.Database; using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Mvc; -using NodaTime; namespace Foxnouns.Backend.Controllers; [Route("/api/v2/meta")] -public class MetaController(DatabaseContext db, IClock clock) : ApiControllerBase +public class MetaController(DatabaseContext db) : ApiControllerBase { - private const string Repository = "https://codeberg.org/pronounscc/pronouns.cc"; - [HttpGet] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(MetaResponse))] public async Task GetMeta() { - var now = clock.GetCurrentInstant(); - var users = await db.Users.Select(u => u.LastActive).ToListAsync(); + var userCount = await db.Users.CountAsync(); var memberCount = await db.Members.CountAsync(); return Ok(new MetaResponse( - Repository, BuildInfo.Version, BuildInfo.Hash, memberCount, - new UserInfo( - users.Count, - users.Count(i => i > now - Duration.FromDays(30)), - users.Count(i => i > now - Duration.FromDays(7)), - users.Count(i => i > now - Duration.FromDays(1)) - )) + BuildInfo.Version, BuildInfo.Hash, memberCount, + new UserInfo(userCount, 0, 0, 0)) ); } [HttpGet("/api/v2/coffee")] public IActionResult BrewCoffee() => Problem("Sorry, I'm a teapot!", statusCode: StatusCodes.Status418ImATeapot); - private record MetaResponse(string Repository, string Version, string Hash, int Members, UserInfo Users); + private record MetaResponse(string Version, string Hash, int Members, UserInfo Users); private record UserInfo(int Total, int ActiveMonth, int ActiveWeek, int ActiveDay); } \ No newline at end of file diff --git a/Foxnouns.Backend/Database/DatabaseContext.cs b/Foxnouns.Backend/Database/DatabaseContext.cs index 5c54ab5..03e2c42 100644 --- a/Foxnouns.Backend/Database/DatabaseContext.cs +++ b/Foxnouns.Backend/Database/DatabaseContext.cs @@ -10,7 +10,6 @@ namespace Foxnouns.Backend.Database; public class DatabaseContext : DbContext { private readonly NpgsqlDataSource _dataSource; - private readonly ILoggerFactory? _loggerFactory; public DbSet Users { get; set; } public DbSet Members { get; set; } @@ -20,7 +19,7 @@ public class DatabaseContext : DbContext public DbSet Applications { get; set; } public DbSet TemporaryKeys { get; set; } - public DatabaseContext(Config config, ILoggerFactory? loggerFactory) + public DatabaseContext(Config config) { var connString = new NpgsqlConnectionStringBuilder(config.Database.Url) { @@ -31,15 +30,13 @@ public class DatabaseContext : DbContext var dataSourceBuilder = new NpgsqlDataSourceBuilder(connString); dataSourceBuilder.UseNodaTime(); _dataSource = dataSourceBuilder.Build(); - _loggerFactory = loggerFactory; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder .ConfigureWarnings(c => c.Ignore(CoreEventId.ManyServiceProvidersCreatedWarning)) .UseNpgsql(_dataSource, o => o.UseNodaTime()) - .UseSnakeCaseNamingConvention() - .UseLoggerFactory(_loggerFactory); + .UseSnakeCaseNamingConvention(); protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) { @@ -76,6 +73,6 @@ public class DesignTimeDatabaseContextFactory : IDesignTimeDbContextFactory() ?? new(); - return new DatabaseContext(config, null); + return new DatabaseContext(config); } } \ No newline at end of file diff --git a/Foxnouns.Backend/Database/DatabaseQueryExtensions.cs b/Foxnouns.Backend/Database/DatabaseQueryExtensions.cs index 8262e5d..9b357fa 100644 --- a/Foxnouns.Backend/Database/DatabaseQueryExtensions.cs +++ b/Foxnouns.Backend/Database/DatabaseQueryExtensions.cs @@ -15,13 +15,11 @@ public static class DatabaseQueryExtensions if (Snowflake.TryParse(userRef, out var snowflake)) { user = await context.Users - .Where(u => !u.Deleted) .FirstOrDefaultAsync(u => u.Id == snowflake); if (user != null) return user; } user = await context.Users - .Where(u => !u.Deleted) .FirstOrDefaultAsync(u => u.Username == userRef); if (user != null) return user; throw new ApiError.NotFound("No user with that ID or username found.", code: ErrorCode.UserNotFound); @@ -30,7 +28,6 @@ public static class DatabaseQueryExtensions public static async Task ResolveUserAsync(this DatabaseContext context, Snowflake id) { var user = await context.Users - .Where(u => !u.Deleted) .FirstOrDefaultAsync(u => u.Id == id); if (user != null) return user; throw new ApiError.NotFound("No user with that ID found.", code: ErrorCode.UserNotFound); @@ -40,7 +37,6 @@ public static class DatabaseQueryExtensions { var member = await context.Members .Include(m => m.User) - .Where(m => !m.User.Deleted) .FirstOrDefaultAsync(m => m.Id == id); if (member != null) return member; throw new ApiError.NotFound("No member with that ID found.", code: ErrorCode.MemberNotFound); @@ -60,14 +56,12 @@ public static class DatabaseQueryExtensions { member = await context.Members .Include(m => m.User) - .Where(m => !m.User.Deleted) .FirstOrDefaultAsync(m => m.Id == snowflake && m.UserId == userId); if (member != null) return member; } member = await context.Members .Include(m => m.User) - .Where(m => !m.User.Deleted) .FirstOrDefaultAsync(m => m.Name == memberRef && m.UserId == userId); if (member != null) return member; throw new ApiError.NotFound("No member with that ID or name found.", code: ErrorCode.MemberNotFound); diff --git a/Foxnouns.Backend/Database/Migrations/20240712233806_AddUserLastActive.Designer.cs b/Foxnouns.Backend/Database/Migrations/20240712233806_AddUserLastActive.Designer.cs deleted file mode 100644 index 0430058..0000000 --- a/Foxnouns.Backend/Database/Migrations/20240712233806_AddUserLastActive.Designer.cs +++ /dev/null @@ -1,515 +0,0 @@ -// -using Foxnouns.Backend.Database; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Foxnouns.Backend.Database.Migrations -{ - [DbContext(typeof(DatabaseContext))] - [Migration("20240712233806_AddUserLastActive")] - partial class AddUserLastActive - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.5") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.Application", b => - { - b.Property("Id") - .HasColumnType("bigint") - .HasColumnName("id"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text") - .HasColumnName("client_id"); - - b.Property("ClientSecret") - .IsRequired() - .HasColumnType("text") - .HasColumnName("client_secret"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text") - .HasColumnName("name"); - - b.Property("RedirectUris") - .IsRequired() - .HasColumnType("text[]") - .HasColumnName("redirect_uris"); - - b.Property("Scopes") - .IsRequired() - .HasColumnType("text[]") - .HasColumnName("scopes"); - - b.HasKey("Id") - .HasName("pk_applications"); - - b.ToTable("applications", (string)null); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.AuthMethod", b => - { - b.Property("Id") - .HasColumnType("bigint") - .HasColumnName("id"); - - b.Property("AuthType") - .HasColumnType("integer") - .HasColumnName("auth_type"); - - b.Property("FediverseApplicationId") - .HasColumnType("bigint") - .HasColumnName("fediverse_application_id"); - - b.Property("RemoteId") - .IsRequired() - .HasColumnType("text") - .HasColumnName("remote_id"); - - b.Property("RemoteUsername") - .HasColumnType("text") - .HasColumnName("remote_username"); - - b.Property("UserId") - .HasColumnType("bigint") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_auth_methods"); - - b.HasIndex("FediverseApplicationId") - .HasDatabaseName("ix_auth_methods_fediverse_application_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_auth_methods_user_id"); - - b.ToTable("auth_methods", (string)null); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.FediverseApplication", b => - { - b.Property("Id") - .HasColumnType("bigint") - .HasColumnName("id"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text") - .HasColumnName("client_id"); - - b.Property("ClientSecret") - .IsRequired() - .HasColumnType("text") - .HasColumnName("client_secret"); - - b.Property("Domain") - .IsRequired() - .HasColumnType("text") - .HasColumnName("domain"); - - b.Property("InstanceType") - .HasColumnType("integer") - .HasColumnName("instance_type"); - - b.HasKey("Id") - .HasName("pk_fediverse_applications"); - - b.ToTable("fediverse_applications", (string)null); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.Member", b => - { - b.Property("Id") - .HasColumnType("bigint") - .HasColumnName("id"); - - b.Property("Avatar") - .HasColumnType("text") - .HasColumnName("avatar"); - - b.Property("Bio") - .HasColumnType("text") - .HasColumnName("bio"); - - b.Property("DisplayName") - .HasColumnType("text") - .HasColumnName("display_name"); - - b.Property("Links") - .IsRequired() - .HasColumnType("text[]") - .HasColumnName("links"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text") - .HasColumnName("name"); - - b.Property("Unlisted") - .HasColumnType("boolean") - .HasColumnName("unlisted"); - - b.Property("UserId") - .HasColumnType("bigint") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_members"); - - b.HasIndex("UserId", "Name") - .IsUnique() - .HasDatabaseName("ix_members_user_id_name"); - - b.ToTable("members", (string)null); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.TemporaryKey", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Expires") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires"); - - b.Property("Key") - .IsRequired() - .HasColumnType("text") - .HasColumnName("key"); - - b.Property("Value") - .IsRequired() - .HasColumnType("text") - .HasColumnName("value"); - - b.HasKey("Id") - .HasName("pk_temporary_keys"); - - b.HasIndex("Key") - .IsUnique() - .HasDatabaseName("ix_temporary_keys_key"); - - b.ToTable("temporary_keys", (string)null); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.Token", b => - { - b.Property("Id") - .HasColumnType("bigint") - .HasColumnName("id"); - - b.Property("ApplicationId") - .HasColumnType("bigint") - .HasColumnName("application_id"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("Hash") - .IsRequired() - .HasColumnType("bytea") - .HasColumnName("hash"); - - b.Property("ManuallyExpired") - .HasColumnType("boolean") - .HasColumnName("manually_expired"); - - b.Property("Scopes") - .IsRequired() - .HasColumnType("text[]") - .HasColumnName("scopes"); - - b.Property("UserId") - .HasColumnType("bigint") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_tokens"); - - b.HasIndex("ApplicationId") - .HasDatabaseName("ix_tokens_application_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_tokens_user_id"); - - b.ToTable("tokens", (string)null); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b => - { - b.Property("Id") - .HasColumnType("bigint") - .HasColumnName("id"); - - b.Property("Avatar") - .HasColumnType("text") - .HasColumnName("avatar"); - - b.Property("Bio") - .HasColumnType("text") - .HasColumnName("bio"); - - b.Property("DisplayName") - .HasColumnType("text") - .HasColumnName("display_name"); - - b.Property("LastActive") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_active"); - - b.Property("Links") - .IsRequired() - .HasColumnType("text[]") - .HasColumnName("links"); - - b.Property("ListHidden") - .HasColumnType("boolean") - .HasColumnName("list_hidden"); - - b.Property("MemberTitle") - .HasColumnType("text") - .HasColumnName("member_title"); - - b.Property("Password") - .HasColumnType("text") - .HasColumnName("password"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("Username") - .IsRequired() - .HasColumnType("text") - .HasColumnName("username"); - - b.HasKey("Id") - .HasName("pk_users"); - - b.HasIndex("Username") - .IsUnique() - .HasDatabaseName("ix_users_username"); - - b.ToTable("users", (string)null); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.AuthMethod", b => - { - b.HasOne("Foxnouns.Backend.Database.Models.FediverseApplication", "FediverseApplication") - .WithMany() - .HasForeignKey("FediverseApplicationId") - .HasConstraintName("fk_auth_methods_fediverse_applications_fediverse_application_id"); - - b.HasOne("Foxnouns.Backend.Database.Models.User", "User") - .WithMany("AuthMethods") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_methods_users_user_id"); - - b.Navigation("FediverseApplication"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.Member", b => - { - b.HasOne("Foxnouns.Backend.Database.Models.User", "User") - .WithMany("Members") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_members_users_user_id"); - - b.OwnsOne("System.Collections.Generic.List", "Fields", b1 => - { - b1.Property("MemberId") - .HasColumnType("bigint"); - - b1.Property("Capacity") - .HasColumnType("integer"); - - b1.HasKey("MemberId"); - - b1.ToTable("members"); - - b1.ToJson("fields"); - - b1.WithOwner() - .HasForeignKey("MemberId") - .HasConstraintName("fk_members_members_id"); - }); - - b.OwnsOne("System.Collections.Generic.List", "Names", b1 => - { - b1.Property("MemberId") - .HasColumnType("bigint"); - - b1.Property("Capacity") - .HasColumnType("integer"); - - b1.HasKey("MemberId"); - - b1.ToTable("members"); - - b1.ToJson("names"); - - b1.WithOwner() - .HasForeignKey("MemberId") - .HasConstraintName("fk_members_members_id"); - }); - - b.OwnsOne("System.Collections.Generic.List", "Pronouns", b1 => - { - b1.Property("MemberId") - .HasColumnType("bigint"); - - b1.Property("Capacity") - .HasColumnType("integer"); - - b1.HasKey("MemberId"); - - b1.ToTable("members"); - - b1.ToJson("pronouns"); - - b1.WithOwner() - .HasForeignKey("MemberId") - .HasConstraintName("fk_members_members_id"); - }); - - b.Navigation("Fields") - .IsRequired(); - - b.Navigation("Names") - .IsRequired(); - - b.Navigation("Pronouns") - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.Token", b => - { - b.HasOne("Foxnouns.Backend.Database.Models.Application", "Application") - .WithMany() - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tokens_applications_application_id"); - - b.HasOne("Foxnouns.Backend.Database.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tokens_users_user_id"); - - b.Navigation("Application"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b => - { - b.OwnsOne("Foxnouns.Backend.Database.Models.User.Fields#List", "Fields", b1 => - { - b1.Property("UserId") - .HasColumnType("bigint"); - - b1.Property("Capacity") - .HasColumnType("integer"); - - b1.HasKey("UserId") - .HasName("pk_users"); - - b1.ToTable("users"); - - b1.ToJson("fields"); - - b1.WithOwner() - .HasForeignKey("UserId") - .HasConstraintName("fk_users_users_user_id"); - }); - - b.OwnsOne("Foxnouns.Backend.Database.Models.User.Names#List", "Names", b1 => - { - b1.Property("UserId") - .HasColumnType("bigint"); - - b1.Property("Capacity") - .HasColumnType("integer"); - - b1.HasKey("UserId") - .HasName("pk_users"); - - b1.ToTable("users"); - - b1.ToJson("names"); - - b1.WithOwner() - .HasForeignKey("UserId") - .HasConstraintName("fk_users_users_user_id"); - }); - - b.OwnsOne("Foxnouns.Backend.Database.Models.User.Pronouns#List", "Pronouns", b1 => - { - b1.Property("UserId") - .HasColumnType("bigint"); - - b1.Property("Capacity") - .HasColumnType("integer"); - - b1.HasKey("UserId") - .HasName("pk_users"); - - b1.ToTable("users"); - - b1.ToJson("pronouns"); - - b1.WithOwner() - .HasForeignKey("UserId") - .HasConstraintName("fk_users_users_user_id"); - }); - - b.Navigation("Fields") - .IsRequired(); - - b.Navigation("Names") - .IsRequired(); - - b.Navigation("Pronouns") - .IsRequired(); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b => - { - b.Navigation("AuthMethods"); - - b.Navigation("Members"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Foxnouns.Backend/Database/Migrations/20240712233806_AddUserLastActive.cs b/Foxnouns.Backend/Database/Migrations/20240712233806_AddUserLastActive.cs deleted file mode 100644 index 8d1392c..0000000 --- a/Foxnouns.Backend/Database/Migrations/20240712233806_AddUserLastActive.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using NodaTime; - -#nullable disable - -namespace Foxnouns.Backend.Database.Migrations -{ - /// - public partial class AddUserLastActive : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "last_active", - table: "users", - type: "timestamp with time zone", - nullable: false, - defaultValueSql: "now()"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "last_active", - table: "users"); - } - } -} diff --git a/Foxnouns.Backend/Database/Migrations/20240713000719_AddDeleted.Designer.cs b/Foxnouns.Backend/Database/Migrations/20240713000719_AddDeleted.Designer.cs deleted file mode 100644 index 8c2dcdc..0000000 --- a/Foxnouns.Backend/Database/Migrations/20240713000719_AddDeleted.Designer.cs +++ /dev/null @@ -1,528 +0,0 @@ -// -using System; -using Foxnouns.Backend.Database; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using NodaTime; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Foxnouns.Backend.Database.Migrations -{ - [DbContext(typeof(DatabaseContext))] - [Migration("20240713000719_AddDeleted")] - partial class AddDeleted - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.5") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.Application", b => - { - b.Property("Id") - .HasColumnType("bigint") - .HasColumnName("id"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text") - .HasColumnName("client_id"); - - b.Property("ClientSecret") - .IsRequired() - .HasColumnType("text") - .HasColumnName("client_secret"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text") - .HasColumnName("name"); - - b.Property("RedirectUris") - .IsRequired() - .HasColumnType("text[]") - .HasColumnName("redirect_uris"); - - b.Property("Scopes") - .IsRequired() - .HasColumnType("text[]") - .HasColumnName("scopes"); - - b.HasKey("Id") - .HasName("pk_applications"); - - b.ToTable("applications", (string)null); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.AuthMethod", b => - { - b.Property("Id") - .HasColumnType("bigint") - .HasColumnName("id"); - - b.Property("AuthType") - .HasColumnType("integer") - .HasColumnName("auth_type"); - - b.Property("FediverseApplicationId") - .HasColumnType("bigint") - .HasColumnName("fediverse_application_id"); - - b.Property("RemoteId") - .IsRequired() - .HasColumnType("text") - .HasColumnName("remote_id"); - - b.Property("RemoteUsername") - .HasColumnType("text") - .HasColumnName("remote_username"); - - b.Property("UserId") - .HasColumnType("bigint") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_auth_methods"); - - b.HasIndex("FediverseApplicationId") - .HasDatabaseName("ix_auth_methods_fediverse_application_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_auth_methods_user_id"); - - b.ToTable("auth_methods", (string)null); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.FediverseApplication", b => - { - b.Property("Id") - .HasColumnType("bigint") - .HasColumnName("id"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text") - .HasColumnName("client_id"); - - b.Property("ClientSecret") - .IsRequired() - .HasColumnType("text") - .HasColumnName("client_secret"); - - b.Property("Domain") - .IsRequired() - .HasColumnType("text") - .HasColumnName("domain"); - - b.Property("InstanceType") - .HasColumnType("integer") - .HasColumnName("instance_type"); - - b.HasKey("Id") - .HasName("pk_fediverse_applications"); - - b.ToTable("fediverse_applications", (string)null); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.Member", b => - { - b.Property("Id") - .HasColumnType("bigint") - .HasColumnName("id"); - - b.Property("Avatar") - .HasColumnType("text") - .HasColumnName("avatar"); - - b.Property("Bio") - .HasColumnType("text") - .HasColumnName("bio"); - - b.Property("DisplayName") - .HasColumnType("text") - .HasColumnName("display_name"); - - b.Property("Links") - .IsRequired() - .HasColumnType("text[]") - .HasColumnName("links"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text") - .HasColumnName("name"); - - b.Property("Unlisted") - .HasColumnType("boolean") - .HasColumnName("unlisted"); - - b.Property("UserId") - .HasColumnType("bigint") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_members"); - - b.HasIndex("UserId", "Name") - .IsUnique() - .HasDatabaseName("ix_members_user_id_name"); - - b.ToTable("members", (string)null); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.TemporaryKey", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Expires") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires"); - - b.Property("Key") - .IsRequired() - .HasColumnType("text") - .HasColumnName("key"); - - b.Property("Value") - .IsRequired() - .HasColumnType("text") - .HasColumnName("value"); - - b.HasKey("Id") - .HasName("pk_temporary_keys"); - - b.HasIndex("Key") - .IsUnique() - .HasDatabaseName("ix_temporary_keys_key"); - - b.ToTable("temporary_keys", (string)null); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.Token", b => - { - b.Property("Id") - .HasColumnType("bigint") - .HasColumnName("id"); - - b.Property("ApplicationId") - .HasColumnType("bigint") - .HasColumnName("application_id"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("Hash") - .IsRequired() - .HasColumnType("bytea") - .HasColumnName("hash"); - - b.Property("ManuallyExpired") - .HasColumnType("boolean") - .HasColumnName("manually_expired"); - - b.Property("Scopes") - .IsRequired() - .HasColumnType("text[]") - .HasColumnName("scopes"); - - b.Property("UserId") - .HasColumnType("bigint") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_tokens"); - - b.HasIndex("ApplicationId") - .HasDatabaseName("ix_tokens_application_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_tokens_user_id"); - - b.ToTable("tokens", (string)null); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b => - { - b.Property("Id") - .HasColumnType("bigint") - .HasColumnName("id"); - - b.Property("Avatar") - .HasColumnType("text") - .HasColumnName("avatar"); - - b.Property("Bio") - .HasColumnType("text") - .HasColumnName("bio"); - - b.Property("Deleted") - .HasColumnType("boolean") - .HasColumnName("deleted"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("DeletedBy") - .HasColumnType("bigint") - .HasColumnName("deleted_by"); - - b.Property("DisplayName") - .HasColumnType("text") - .HasColumnName("display_name"); - - b.Property("LastActive") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_active"); - - b.Property("Links") - .IsRequired() - .HasColumnType("text[]") - .HasColumnName("links"); - - b.Property("ListHidden") - .HasColumnType("boolean") - .HasColumnName("list_hidden"); - - b.Property("MemberTitle") - .HasColumnType("text") - .HasColumnName("member_title"); - - b.Property("Password") - .HasColumnType("text") - .HasColumnName("password"); - - b.Property("Role") - .HasColumnType("integer") - .HasColumnName("role"); - - b.Property("Username") - .IsRequired() - .HasColumnType("text") - .HasColumnName("username"); - - b.HasKey("Id") - .HasName("pk_users"); - - b.HasIndex("Username") - .IsUnique() - .HasDatabaseName("ix_users_username"); - - b.ToTable("users", (string)null); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.AuthMethod", b => - { - b.HasOne("Foxnouns.Backend.Database.Models.FediverseApplication", "FediverseApplication") - .WithMany() - .HasForeignKey("FediverseApplicationId") - .HasConstraintName("fk_auth_methods_fediverse_applications_fediverse_application_id"); - - b.HasOne("Foxnouns.Backend.Database.Models.User", "User") - .WithMany("AuthMethods") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_methods_users_user_id"); - - b.Navigation("FediverseApplication"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.Member", b => - { - b.HasOne("Foxnouns.Backend.Database.Models.User", "User") - .WithMany("Members") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_members_users_user_id"); - - b.OwnsOne("System.Collections.Generic.List", "Fields", b1 => - { - b1.Property("MemberId") - .HasColumnType("bigint"); - - b1.Property("Capacity") - .HasColumnType("integer"); - - b1.HasKey("MemberId"); - - b1.ToTable("members"); - - b1.ToJson("fields"); - - b1.WithOwner() - .HasForeignKey("MemberId") - .HasConstraintName("fk_members_members_id"); - }); - - b.OwnsOne("System.Collections.Generic.List", "Names", b1 => - { - b1.Property("MemberId") - .HasColumnType("bigint"); - - b1.Property("Capacity") - .HasColumnType("integer"); - - b1.HasKey("MemberId"); - - b1.ToTable("members"); - - b1.ToJson("names"); - - b1.WithOwner() - .HasForeignKey("MemberId") - .HasConstraintName("fk_members_members_id"); - }); - - b.OwnsOne("System.Collections.Generic.List", "Pronouns", b1 => - { - b1.Property("MemberId") - .HasColumnType("bigint"); - - b1.Property("Capacity") - .HasColumnType("integer"); - - b1.HasKey("MemberId"); - - b1.ToTable("members"); - - b1.ToJson("pronouns"); - - b1.WithOwner() - .HasForeignKey("MemberId") - .HasConstraintName("fk_members_members_id"); - }); - - b.Navigation("Fields") - .IsRequired(); - - b.Navigation("Names") - .IsRequired(); - - b.Navigation("Pronouns") - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.Token", b => - { - b.HasOne("Foxnouns.Backend.Database.Models.Application", "Application") - .WithMany() - .HasForeignKey("ApplicationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tokens_applications_application_id"); - - b.HasOne("Foxnouns.Backend.Database.Models.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tokens_users_user_id"); - - b.Navigation("Application"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b => - { - b.OwnsOne("Foxnouns.Backend.Database.Models.User.Fields#List", "Fields", b1 => - { - b1.Property("UserId") - .HasColumnType("bigint"); - - b1.Property("Capacity") - .HasColumnType("integer"); - - b1.HasKey("UserId") - .HasName("pk_users"); - - b1.ToTable("users"); - - b1.ToJson("fields"); - - b1.WithOwner() - .HasForeignKey("UserId") - .HasConstraintName("fk_users_users_user_id"); - }); - - b.OwnsOne("Foxnouns.Backend.Database.Models.User.Names#List", "Names", b1 => - { - b1.Property("UserId") - .HasColumnType("bigint"); - - b1.Property("Capacity") - .HasColumnType("integer"); - - b1.HasKey("UserId") - .HasName("pk_users"); - - b1.ToTable("users"); - - b1.ToJson("names"); - - b1.WithOwner() - .HasForeignKey("UserId") - .HasConstraintName("fk_users_users_user_id"); - }); - - b.OwnsOne("Foxnouns.Backend.Database.Models.User.Pronouns#List", "Pronouns", b1 => - { - b1.Property("UserId") - .HasColumnType("bigint"); - - b1.Property("Capacity") - .HasColumnType("integer"); - - b1.HasKey("UserId") - .HasName("pk_users"); - - b1.ToTable("users"); - - b1.ToJson("pronouns"); - - b1.WithOwner() - .HasForeignKey("UserId") - .HasConstraintName("fk_users_users_user_id"); - }); - - b.Navigation("Fields") - .IsRequired(); - - b.Navigation("Names") - .IsRequired(); - - b.Navigation("Pronouns") - .IsRequired(); - }); - - modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b => - { - b.Navigation("AuthMethods"); - - b.Navigation("Members"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Foxnouns.Backend/Database/Migrations/20240713000719_AddDeleted.cs b/Foxnouns.Backend/Database/Migrations/20240713000719_AddDeleted.cs deleted file mode 100644 index a14ad5c..0000000 --- a/Foxnouns.Backend/Database/Migrations/20240713000719_AddDeleted.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using NodaTime; - -#nullable disable - -namespace Foxnouns.Backend.Database.Migrations -{ - /// - public partial class AddDeleted : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "deleted", - table: "users", - type: "boolean", - nullable: false, - defaultValue: false); - - migrationBuilder.AddColumn( - name: "deleted_at", - table: "users", - type: "timestamp with time zone", - nullable: true); - - migrationBuilder.AddColumn( - name: "deleted_by", - table: "users", - type: "bigint", - nullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "deleted", - table: "users"); - - migrationBuilder.DropColumn( - name: "deleted_at", - table: "users"); - - migrationBuilder.DropColumn( - name: "deleted_by", - table: "users"); - } - } -} diff --git a/Foxnouns.Backend/Database/Migrations/DatabaseContextModelSnapshot.cs b/Foxnouns.Backend/Database/Migrations/DatabaseContextModelSnapshot.cs index 4440499..360d43d 100644 --- a/Foxnouns.Backend/Database/Migrations/DatabaseContextModelSnapshot.cs +++ b/Foxnouns.Backend/Database/Migrations/DatabaseContextModelSnapshot.cs @@ -267,26 +267,10 @@ namespace Foxnouns.Backend.Database.Migrations .HasColumnType("text") .HasColumnName("bio"); - b.Property("Deleted") - .HasColumnType("boolean") - .HasColumnName("deleted"); - - b.Property("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property("DeletedBy") - .HasColumnType("bigint") - .HasColumnName("deleted_by"); - b.Property("DisplayName") .HasColumnType("text") .HasColumnName("display_name"); - b.Property("LastActive") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_active"); - b.Property("Links") .IsRequired() .HasColumnType("text[]") @@ -351,7 +335,7 @@ namespace Foxnouns.Backend.Database.Migrations .IsRequired() .HasConstraintName("fk_members_users_user_id"); - b.OwnsOne("System.Collections.Generic.List", "Fields", b1 => + b.OwnsOne("Foxnouns.Backend.Database.Models.Member.Fields#System.Collections.Generic.List", "Fields", b1 => { b1.Property("MemberId") .HasColumnType("bigint"); @@ -361,7 +345,7 @@ namespace Foxnouns.Backend.Database.Migrations b1.HasKey("MemberId"); - b1.ToTable("members"); + b1.ToTable("members", (string)null); b1.ToJson("fields"); @@ -370,7 +354,7 @@ namespace Foxnouns.Backend.Database.Migrations .HasConstraintName("fk_members_members_id"); }); - b.OwnsOne("System.Collections.Generic.List", "Names", b1 => + b.OwnsOne("Foxnouns.Backend.Database.Models.Member.Names#System.Collections.Generic.List", "Names", b1 => { b1.Property("MemberId") .HasColumnType("bigint"); @@ -380,7 +364,7 @@ namespace Foxnouns.Backend.Database.Migrations b1.HasKey("MemberId"); - b1.ToTable("members"); + b1.ToTable("members", (string)null); b1.ToJson("names"); @@ -389,7 +373,7 @@ namespace Foxnouns.Backend.Database.Migrations .HasConstraintName("fk_members_members_id"); }); - b.OwnsOne("System.Collections.Generic.List", "Pronouns", b1 => + b.OwnsOne("Foxnouns.Backend.Database.Models.Member.Pronouns#System.Collections.Generic.List", "Pronouns", b1 => { b1.Property("MemberId") .HasColumnType("bigint"); @@ -399,7 +383,7 @@ namespace Foxnouns.Backend.Database.Migrations b1.HasKey("MemberId"); - b1.ToTable("members"); + b1.ToTable("members", (string)null); b1.ToJson("pronouns"); @@ -443,7 +427,7 @@ namespace Foxnouns.Backend.Database.Migrations modelBuilder.Entity("Foxnouns.Backend.Database.Models.User", b => { - b.OwnsOne("Foxnouns.Backend.Database.Models.User.Fields#List", "Fields", b1 => + b.OwnsOne("Foxnouns.Backend.Database.Models.User.Fields#Foxnouns.Backend.Database.Models.User.Fields#List", "Fields", b1 => { b1.Property("UserId") .HasColumnType("bigint"); @@ -454,7 +438,7 @@ namespace Foxnouns.Backend.Database.Migrations b1.HasKey("UserId") .HasName("pk_users"); - b1.ToTable("users"); + b1.ToTable("users", (string)null); b1.ToJson("fields"); @@ -463,7 +447,7 @@ namespace Foxnouns.Backend.Database.Migrations .HasConstraintName("fk_users_users_user_id"); }); - b.OwnsOne("Foxnouns.Backend.Database.Models.User.Names#List", "Names", b1 => + b.OwnsOne("Foxnouns.Backend.Database.Models.User.Names#Foxnouns.Backend.Database.Models.User.Names#List", "Names", b1 => { b1.Property("UserId") .HasColumnType("bigint"); @@ -474,7 +458,7 @@ namespace Foxnouns.Backend.Database.Migrations b1.HasKey("UserId") .HasName("pk_users"); - b1.ToTable("users"); + b1.ToTable("users", (string)null); b1.ToJson("names"); @@ -483,7 +467,7 @@ namespace Foxnouns.Backend.Database.Migrations .HasConstraintName("fk_users_users_user_id"); }); - b.OwnsOne("Foxnouns.Backend.Database.Models.User.Pronouns#List", "Pronouns", b1 => + b.OwnsOne("Foxnouns.Backend.Database.Models.User.Pronouns#Foxnouns.Backend.Database.Models.User.Pronouns#List", "Pronouns", b1 => { b1.Property("UserId") .HasColumnType("bigint"); @@ -494,7 +478,7 @@ namespace Foxnouns.Backend.Database.Migrations b1.HasKey("UserId") .HasName("pk_users"); - b1.ToTable("users"); + b1.ToTable("users", (string)null); b1.ToJson("pronouns"); diff --git a/Foxnouns.Backend/Database/Models/User.cs b/Foxnouns.Backend/Database/Models/User.cs index b0e060c..986c779 100644 --- a/Foxnouns.Backend/Database/Models/User.cs +++ b/Foxnouns.Backend/Database/Models/User.cs @@ -1,5 +1,4 @@ -using System.ComponentModel.DataAnnotations.Schema; -using NodaTime; +using System.Text.RegularExpressions; namespace Foxnouns.Backend.Database.Models; @@ -22,13 +21,6 @@ public class User : BaseModel public List Members { get; } = []; public List AuthMethods { get; } = []; - - public required Instant LastActive { get; set; } - - public bool Deleted { get; set; } - public Instant? DeletedAt { get; set; } - public Snowflake? DeletedBy { get; set; } - [NotMapped] public bool? SelfDelete => Deleted ? DeletedBy != null : null; } public enum UserRole diff --git a/Foxnouns.Backend/Extensions/WebApplicationExtensions.cs b/Foxnouns.Backend/Extensions/WebApplicationExtensions.cs index bcd29f4..0bf334c 100644 --- a/Foxnouns.Backend/Extensions/WebApplicationExtensions.cs +++ b/Foxnouns.Backend/Extensions/WebApplicationExtensions.cs @@ -1,6 +1,3 @@ -using App.Metrics; -using App.Metrics.AspNetCore; -using App.Metrics.Formatters.Prometheus; using Foxnouns.Backend.Database; using Foxnouns.Backend.Jobs; using Foxnouns.Backend.Middleware; @@ -9,7 +6,6 @@ using Microsoft.EntityFrameworkCore; using NodaTime; using Serilog; using Serilog.Events; -using IClock = NodaTime.IClock; namespace Foxnouns.Backend.Extensions; @@ -28,12 +24,9 @@ public static class WebApplicationExtensions // ASP.NET's built in request logs are extremely verbose, so we use Serilog's instead. // Serilog doesn't disable the built-in logs, so we do it here. .MinimumLevel.Override("Microsoft", LogEventLevel.Information) - .MinimumLevel.Override("Microsoft.EntityFrameworkCore.Database.Command", - config.Logging.LogQueries ? LogEventLevel.Information : LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.Hosting", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.Mvc", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.Routing", LogEventLevel.Warning) - .MinimumLevel.Override("Hangfire", LogEventLevel.Information) .WriteTo.Console(); if (config.Logging.SeqLogUrl != null) @@ -41,8 +34,10 @@ public static class WebApplicationExtensions logCfg.WriteTo.Seq(config.Logging.SeqLogUrl, restrictedToMinimumLevel: LogEventLevel.Verbose); } + Log.Logger = logCfg.CreateLogger(); + // AddSerilog doesn't seem to add an ILogger to the service collection, so add that manually. - builder.Services.AddSerilog().AddSingleton(Log.Logger = logCfg.CreateLogger()); + builder.Services.AddSerilog().AddSingleton(Log.Logger); return builder; } @@ -57,36 +52,6 @@ public static class WebApplicationExtensions return config; } - public static WebApplicationBuilder AddMetrics(this WebApplicationBuilder builder) - { - var config = builder.Configuration.Get() ?? new(); - var metrics = AppMetrics.CreateDefaultBuilder() - .OutputMetrics.AsPrometheusPlainText() - .Build(); - - builder.Services.AddSingleton(metrics); - builder.Services.AddSingleton(metrics); - - builder.WebHost - .ConfigureMetrics(metrics) - .UseMetrics(opts => - { - opts.EndpointOptions = options => - { - // Metrics must listen on a separate port for security reasons. If no metrics port is set, disable the endpoint entirely. - options.MetricsEndpointEnabled = config.Logging.MetricsPort != null; - options.EnvironmentInfoEndpointEnabled = config.Logging.MetricsPort != null; - options.MetricsTextEndpointEnabled = false; - options.MetricsEndpointOutputFormatter = metrics.OutputMetricsFormatters - .OfType().First(); - }; - }) - .UseMetricsWebTracking() - .ConfigureAppMetricsHostingConfiguration(opts => { opts.AllEndpointsPort = config.Logging.MetricsPort; }); - - return builder; - } - public static IConfigurationBuilder AddConfiguration(this IConfigurationBuilder builder) { var file = Environment.GetEnvironmentVariable("FOXNOUNS_CONFIG_FILE") ?? "config.ini"; diff --git a/Foxnouns.Backend/Foxnouns.Backend.csproj b/Foxnouns.Backend/Foxnouns.Backend.csproj index b43e2df..d554482 100644 --- a/Foxnouns.Backend/Foxnouns.Backend.csproj +++ b/Foxnouns.Backend/Foxnouns.Backend.csproj @@ -6,9 +6,6 @@ - - - diff --git a/Foxnouns.Backend/Metrics.cs b/Foxnouns.Backend/Metrics.cs deleted file mode 100644 index a830ab8..0000000 --- a/Foxnouns.Backend/Metrics.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Foxnouns.Backend; - -public static class Metrics -{ - -} \ No newline at end of file diff --git a/Foxnouns.Backend/Program.cs b/Foxnouns.Backend/Program.cs index 7230598..4a311a7 100644 --- a/Foxnouns.Backend/Program.cs +++ b/Foxnouns.Backend/Program.cs @@ -21,7 +21,7 @@ var builder = WebApplication.CreateBuilder(args); var config = builder.AddConfiguration(); -builder.AddSerilog().AddMetrics(); +builder.AddSerilog(); builder.WebHost .UseSentry(opts => @@ -75,9 +75,9 @@ builder.Services .Build()); builder.Services.AddHangfire(c => c.UseSentry().UseRedisStorage(config.Jobs.Redis, new RedisStorageOptions - { - Prefix = "foxnouns_" - })) +{ + Prefix = "foxnouns_" +})) .AddHangfireServer(options => { options.WorkerCount = config.Jobs.Workers; }); var app = builder.Build(); @@ -103,7 +103,6 @@ app.UseHangfireDashboard("/hangfire", new DashboardOptions app.Urls.Clear(); app.Urls.Add(config.Address); -if (config.MetricsAddress != null) app.Urls.Add(config.MetricsAddress); // Fire off the periodic tasks loop in the background _ = new Timer(_ => diff --git a/Foxnouns.Backend/Services/AuthService.cs b/Foxnouns.Backend/Services/AuthService.cs index b4a1adf..a87a50c 100644 --- a/Foxnouns.Backend/Services/AuthService.cs +++ b/Foxnouns.Backend/Services/AuthService.cs @@ -8,7 +8,7 @@ using NodaTime; namespace Foxnouns.Backend.Services; -public class AuthService(ILogger logger, IClock clock, DatabaseContext db, ISnowflakeGenerator snowflakeGenerator) +public class AuthService(ILogger logger, DatabaseContext db, ISnowflakeGenerator snowflakeGenerator) { private readonly PasswordHasher _passwordHasher = new(); @@ -26,8 +26,7 @@ public class AuthService(ILogger logger, IClock clock, DatabaseContext db, ISnow { new AuthMethod { Id = snowflakeGenerator.GenerateSnowflake(), AuthType = AuthType.Email, RemoteId = email } - }, - LastActive = clock.GetCurrentInstant() + } }; db.Add(user); @@ -60,8 +59,7 @@ public class AuthService(ILogger logger, IClock clock, DatabaseContext db, ISnow Id = snowflakeGenerator.GenerateSnowflake(), AuthType = authType, RemoteId = remoteId, RemoteUsername = remoteUsername, FediverseApplication = instance } - }, - LastActive = clock.GetCurrentInstant() + } }; db.Add(user); diff --git a/Foxnouns.Backend/Services/UserRendererService.cs b/Foxnouns.Backend/Services/UserRendererService.cs index 2a3754a..ca5fff4 100644 --- a/Foxnouns.Backend/Services/UserRendererService.cs +++ b/Foxnouns.Backend/Services/UserRendererService.cs @@ -3,7 +3,6 @@ using Foxnouns.Backend.Database.Models; using Foxnouns.Backend.Utils; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; -using NodaTime; namespace Foxnouns.Backend.Services; @@ -15,12 +14,12 @@ public class UserRendererService(DatabaseContext db, MemberRendererService membe bool renderAuthMethods = false) { var isSelfUser = selfUser?.Id == user.Id; - var tokenCanReadHiddenMembers = token.HasScope("member.read") && isSelfUser; - var tokenPrivileged = token.HasScope("user.read_privileged") && isSelfUser; + var tokenCanReadHiddenMembers = token.HasScope("member.read"); + var tokenCanReadAuth = token.HasScope("user.read_privileged"); renderMembers = renderMembers && - (!user.ListHidden || tokenCanReadHiddenMembers); - renderAuthMethods = renderAuthMethods && tokenPrivileged; + (!user.ListHidden || (isSelfUser && tokenCanReadHiddenMembers)); + renderAuthMethods = renderAuthMethods && isSelfUser && tokenCanReadAuth; IEnumerable members = renderMembers ? await db.Members.Where(m => m.UserId == user.Id).ToListAsync() : []; @@ -35,8 +34,7 @@ public class UserRendererService(DatabaseContext db, MemberRendererService membe : []; return new UserResponse( - user.Id, user.Username, user.DisplayName, user.Bio, user.MemberTitle, AvatarUrlFor(user), user.Links, - user.Names, + user.Id, user.Username, user.DisplayName, user.Bio, user.MemberTitle, AvatarUrlFor(user), user.Links, user.Names, user.Pronouns, user.Fields, renderMembers ? members.Select(memberRendererService.RenderPartialMember) : null, renderAuthMethods @@ -44,8 +42,7 @@ public class UserRendererService(DatabaseContext db, MemberRendererService membe a.Id, a.AuthType, a.RemoteId, a.RemoteUsername, a.FediverseApplication?.Domain )) - : null, - tokenPrivileged ? user.LastActive : null + : null ); } @@ -66,9 +63,7 @@ public class UserRendererService(DatabaseContext db, MemberRendererService membe [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] IEnumerable? Members, [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] - IEnumerable? AuthMethods, - [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] - Instant? LastActive + IEnumerable? AuthMethods ); public record AuthenticationMethodResponse( diff --git a/Foxnouns.Backend/Utils/AuthUtils.cs b/Foxnouns.Backend/Utils/AuthUtils.cs index 39d5870..8dfb137 100644 --- a/Foxnouns.Backend/Utils/AuthUtils.cs +++ b/Foxnouns.Backend/Utils/AuthUtils.cs @@ -75,6 +75,7 @@ public static class AuthUtils } catch (Exception e) { + Console.WriteLine($"Error converting string: {e}"); bytes = []; return false; } diff --git a/Foxnouns.Backend/config.example.ini b/Foxnouns.Backend/config.example.ini index e316c8e..c791d1f 100644 --- a/Foxnouns.Backend/config.example.ini +++ b/Foxnouns.Backend/config.example.ini @@ -18,14 +18,11 @@ SentryUrl = https://examplePublicKey@o0.ingest.sentry.io/0 SentryTracing = true ; Percentage of performance traces to send to Sentry (optional). Defaults to 0.0 (no traces at all) SentryTracesSampleRate = 1.0 -; Whether to log SQL queries. Note that this is very verbose. Defaults to false. -LogQueries = false -; The port the /metrics endpoint will listen on. If not set, metrics will be disabled. -MetricsPort = 5001 [Database] ; The database URL in ADO.NET format. Url = "Host=localhost;Database=foxnouns_net;Username=pronouns;Password=pronouns" + ; The timeout for opening new connections. Defaults to 5. Timeout = 5 ; The maximum number of open connections. Defaults to 50. diff --git a/SCOPES.md b/SCOPES.md new file mode 100644 index 0000000..5cab914 --- /dev/null +++ b/SCOPES.md @@ -0,0 +1,18 @@ +# List of API endpoints and scopes + +## Scopes + +- `identify`: `@me` will refer to token user (always granted) +- `user.read_privileged`: can read privileged information such as authentication methods +- `user.update`: can update the user's profile. + **cannot** update anything locked behind `user.read_privileged` +- `member.read`: can view member list if it's hidden and enumerate unlisted members +- `member.create`: can create new members +- `member.update`: can edit and delete members + +## Users + +- GET `/users/{userRef}`: `identify` required to use `@me` as user reference. + `user.read_privileged` required to view authentication methods. + `member.read` required to view unlisted members. +- PATCH `/users/@me`: `user.update` required. \ No newline at end of file