using System.Security.Cryptography; using Foxnouns.Backend.Database.Models; using Foxnouns.Backend.Utils; using Microsoft.EntityFrameworkCore; using NodaTime; namespace Foxnouns.Backend.Database; public static class DatabaseQueryExtensions { public static async Task ResolveUserAsync(this DatabaseContext context, string userRef, Token? token, CancellationToken ct = default) { if (userRef == "@me") { return token != null ? await context.Users.FirstAsync(u => u.Id == token.UserId, ct) : throw new ApiError.Unauthorized("This endpoint requires an authenticated user.", ErrorCode.AuthenticationRequired); } User? user; if (Snowflake.TryParse(userRef, out var snowflake)) { user = await context.Users .Where(u => !u.Deleted) .FirstOrDefaultAsync(u => u.Id == snowflake, ct); if (user != null) return user; } user = await context.Users .Where(u => !u.Deleted) .FirstOrDefaultAsync(u => u.Username == userRef, ct); if (user != null) return user; throw new ApiError.NotFound("No user with that ID or username found.", code: ErrorCode.UserNotFound); } public static async Task ResolveUserAsync(this DatabaseContext context, Snowflake id, CancellationToken ct = default) { var user = await context.Users .Where(u => !u.Deleted) .FirstOrDefaultAsync(u => u.Id == id, ct); if (user != null) return user; throw new ApiError.NotFound("No user with that ID found.", code: ErrorCode.UserNotFound); } public static async Task ResolveMemberAsync(this DatabaseContext context, Snowflake id, CancellationToken ct = default) { var member = await context.Members .Include(m => m.User) .Where(m => !m.User.Deleted) .FirstOrDefaultAsync(m => m.Id == id, ct); if (member != null) return member; throw new ApiError.NotFound("No member with that ID found.", code: ErrorCode.MemberNotFound); } public static async Task ResolveMemberAsync(this DatabaseContext context, string userRef, string memberRef, Token? token, CancellationToken ct = default) { var user = await context.ResolveUserAsync(userRef, token, ct); return await context.ResolveMemberAsync(user.Id, memberRef, ct); } public static async Task ResolveMemberAsync(this DatabaseContext context, Snowflake userId, string memberRef, CancellationToken ct = default) { Member? member; if (Snowflake.TryParse(memberRef, out var snowflake)) { member = await context.Members .Include(m => m.User) .Where(m => !m.User.Deleted) .FirstOrDefaultAsync(m => m.Id == snowflake && m.UserId == userId, ct); 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, ct); if (member != null) return member; throw new ApiError.NotFound("No member with that ID or name found.", code: ErrorCode.MemberNotFound); } public static async Task GetFrontendApplicationAsync(this DatabaseContext context, CancellationToken ct = default) { var app = await context.Applications.FirstOrDefaultAsync(a => a.Id == new Snowflake(0), ct); if (app != null) return app; app = new Application { Id = new Snowflake(0), ClientId = RandomNumberGenerator.GetHexString(32, true), ClientSecret = AuthUtils.RandomToken(), Name = "pronouns.cc", Scopes = ["*"], RedirectUris = [], }; context.Add(app); await context.SaveChangesAsync(ct); return app; } public static async Task GetToken(this DatabaseContext context, byte[] rawToken, CancellationToken ct = default) { var hash = SHA512.HashData(rawToken); var oauthToken = await context.Tokens .Include(t => t.Application) .Include(t => t.User) .FirstOrDefaultAsync( t => t.Hash == hash && t.ExpiresAt > SystemClock.Instance.GetCurrentInstant() && !t.ManuallyExpired, ct); return oauthToken; } public static async Task GetTokenUserId(this DatabaseContext context, byte[] rawToken, CancellationToken ct = default) { var hash = SHA512.HashData(rawToken); return await context.Tokens .Where(t => t.Hash == hash && t.ExpiresAt > SystemClock.Instance.GetCurrentInstant() && !t.ManuallyExpired) .Select(t => t.UserId).FirstOrDefaultAsync(ct); } }