2024-05-28 15:29:18 +02:00
|
|
|
using System.Security.Cryptography;
|
|
|
|
using Foxnouns.Backend.Database.Models;
|
|
|
|
using Foxnouns.Backend.Utils;
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
2024-06-10 16:25:49 +02:00
|
|
|
using NodaTime;
|
2024-05-28 15:29:18 +02:00
|
|
|
|
|
|
|
namespace Foxnouns.Backend.Database;
|
|
|
|
|
|
|
|
public static class DatabaseQueryExtensions
|
|
|
|
{
|
2024-09-05 21:10:45 +02:00
|
|
|
public static async Task<User> ResolveUserAsync(this DatabaseContext context, string userRef, Token? token,
|
|
|
|
CancellationToken ct = default)
|
2024-05-28 15:29:18 +02:00
|
|
|
{
|
2024-09-05 21:10:45 +02:00
|
|
|
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);
|
|
|
|
}
|
2024-07-13 19:38:40 +02:00
|
|
|
|
2024-05-28 15:29:18 +02:00
|
|
|
User? user;
|
|
|
|
if (Snowflake.TryParse(userRef, out var snowflake))
|
|
|
|
{
|
|
|
|
user = await context.Users
|
2024-07-13 03:09:00 +02:00
|
|
|
.Where(u => !u.Deleted)
|
2024-09-05 21:10:45 +02:00
|
|
|
.FirstOrDefaultAsync(u => u.Id == snowflake, ct);
|
2024-05-28 15:29:18 +02:00
|
|
|
if (user != null) return user;
|
|
|
|
}
|
|
|
|
|
|
|
|
user = await context.Users
|
2024-07-13 03:09:00 +02:00
|
|
|
.Where(u => !u.Deleted)
|
2024-09-05 21:10:45 +02:00
|
|
|
.FirstOrDefaultAsync(u => u.Username == userRef, ct);
|
2024-05-28 15:29:18 +02:00
|
|
|
if (user != null) return user;
|
2024-05-28 17:09:50 +02:00
|
|
|
throw new ApiError.NotFound("No user with that ID or username found.", code: ErrorCode.UserNotFound);
|
2024-05-28 15:29:18 +02:00
|
|
|
}
|
|
|
|
|
2024-09-09 14:37:59 +02:00
|
|
|
public static async Task<User> ResolveUserAsync(this DatabaseContext context, Snowflake id,
|
|
|
|
CancellationToken ct = default)
|
2024-05-30 16:59:40 +02:00
|
|
|
{
|
|
|
|
var user = await context.Users
|
2024-07-13 03:09:00 +02:00
|
|
|
.Where(u => !u.Deleted)
|
2024-09-09 14:37:59 +02:00
|
|
|
.FirstOrDefaultAsync(u => u.Id == id, ct);
|
2024-05-30 16:59:40 +02:00
|
|
|
if (user != null) return user;
|
|
|
|
throw new ApiError.NotFound("No user with that ID found.", code: ErrorCode.UserNotFound);
|
|
|
|
}
|
|
|
|
|
2024-09-09 14:37:59 +02:00
|
|
|
public static async Task<Member> ResolveMemberAsync(this DatabaseContext context, Snowflake id,
|
|
|
|
CancellationToken ct = default)
|
2024-05-30 16:59:40 +02:00
|
|
|
{
|
|
|
|
var member = await context.Members
|
|
|
|
.Include(m => m.User)
|
2024-07-13 03:09:00 +02:00
|
|
|
.Where(m => !m.User.Deleted)
|
2024-09-09 14:37:59 +02:00
|
|
|
.FirstOrDefaultAsync(m => m.Id == id, ct);
|
2024-05-30 16:59:40 +02:00
|
|
|
if (member != null) return member;
|
|
|
|
throw new ApiError.NotFound("No member with that ID found.", code: ErrorCode.MemberNotFound);
|
|
|
|
}
|
|
|
|
|
2024-09-04 14:25:44 +02:00
|
|
|
public static async Task<Member> ResolveMemberAsync(this DatabaseContext context, string userRef, string memberRef,
|
2024-09-09 14:37:59 +02:00
|
|
|
Token? token, CancellationToken ct = default)
|
2024-05-30 16:59:40 +02:00
|
|
|
{
|
2024-09-09 14:37:59 +02:00
|
|
|
var user = await context.ResolveUserAsync(userRef, token, ct);
|
|
|
|
return await context.ResolveMemberAsync(user.Id, memberRef, ct);
|
2024-05-30 16:59:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public static async Task<Member> ResolveMemberAsync(this DatabaseContext context, Snowflake userId,
|
2024-09-09 14:37:59 +02:00
|
|
|
string memberRef, CancellationToken ct = default)
|
2024-05-30 16:59:40 +02:00
|
|
|
{
|
|
|
|
Member? member;
|
|
|
|
if (Snowflake.TryParse(memberRef, out var snowflake))
|
|
|
|
{
|
|
|
|
member = await context.Members
|
|
|
|
.Include(m => m.User)
|
2024-09-29 19:52:22 +02:00
|
|
|
.Include(m => m.ProfileFlags)
|
2024-07-13 03:09:00 +02:00
|
|
|
.Where(m => !m.User.Deleted)
|
2024-09-09 14:37:59 +02:00
|
|
|
.FirstOrDefaultAsync(m => m.Id == snowflake && m.UserId == userId, ct);
|
2024-05-30 16:59:40 +02:00
|
|
|
if (member != null) return member;
|
|
|
|
}
|
|
|
|
|
|
|
|
member = await context.Members
|
|
|
|
.Include(m => m.User)
|
2024-09-29 19:52:22 +02:00
|
|
|
.Include(m => m.ProfileFlags)
|
2024-07-13 03:09:00 +02:00
|
|
|
.Where(m => !m.User.Deleted)
|
2024-09-09 14:37:59 +02:00
|
|
|
.FirstOrDefaultAsync(m => m.Name == memberRef && m.UserId == userId, ct);
|
2024-05-30 16:59:40 +02:00
|
|
|
if (member != null) return member;
|
|
|
|
throw new ApiError.NotFound("No member with that ID or name found.", code: ErrorCode.MemberNotFound);
|
|
|
|
}
|
|
|
|
|
2024-09-09 14:37:59 +02:00
|
|
|
public static async Task<Application> GetFrontendApplicationAsync(this DatabaseContext context,
|
|
|
|
CancellationToken ct = default)
|
2024-05-28 15:29:18 +02:00
|
|
|
{
|
2024-09-09 14:37:59 +02:00
|
|
|
var app = await context.Applications.FirstOrDefaultAsync(a => a.Id == new Snowflake(0), ct);
|
2024-05-28 15:29:18 +02:00
|
|
|
if (app != null) return app;
|
|
|
|
|
|
|
|
app = new Application
|
|
|
|
{
|
|
|
|
Id = new Snowflake(0),
|
|
|
|
ClientId = RandomNumberGenerator.GetHexString(32, true),
|
2024-09-14 16:37:52 +02:00
|
|
|
ClientSecret = AuthUtils.RandomToken(),
|
2024-05-28 15:29:18 +02:00
|
|
|
Name = "pronouns.cc",
|
|
|
|
Scopes = ["*"],
|
|
|
|
RedirectUris = [],
|
|
|
|
};
|
|
|
|
|
|
|
|
context.Add(app);
|
2024-09-09 14:37:59 +02:00
|
|
|
await context.SaveChangesAsync(ct);
|
2024-05-28 15:29:18 +02:00
|
|
|
return app;
|
|
|
|
}
|
2024-09-11 16:23:45 +02:00
|
|
|
|
|
|
|
public static async Task<Token?> GetToken(this DatabaseContext context, byte[] rawToken,
|
|
|
|
CancellationToken ct = default)
|
|
|
|
{
|
|
|
|
var hash = SHA512.HashData(rawToken);
|
2024-09-11 16:34:08 +02:00
|
|
|
|
2024-09-11 16:23:45 +02:00
|
|
|
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;
|
|
|
|
}
|
2024-09-11 16:34:08 +02:00
|
|
|
|
|
|
|
public static async Task<Snowflake?> 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);
|
|
|
|
}
|
2024-05-28 15:29:18 +02:00
|
|
|
}
|